mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-22 20:12:37 -07:00
Compare commits
401 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6620dcde6b | |||
| 60966d5bb1 | |||
| ea22a53bf2 | |||
| 7b9526b4ed | |||
| 676074187a | |||
| 5dd2c31caf | |||
| 2db400a1a0 | |||
| b68dbaf15e | |||
| 84febcdf95 | |||
| c972ef90c8 | |||
| 19a74e3130 | |||
| 5ba789f782 | |||
| 58b5501e17 | |||
| b584832b8f | |||
| fc0cf17c4d | |||
| 001dd369ec | |||
| 9ce2ea4a5c | |||
| eec8814c22 | |||
| 7a6ed68482 | |||
| cd9e23f2de | |||
| ffa84de0bc | |||
| 89d3cdba17 | |||
| 2ba5843f22 | |||
| c4d0f08767 | |||
| db1cdec2a2 | |||
| 1eea1a6a22 | |||
| 4a69ce5a98 | |||
| 8d653cba9b | |||
| a6126a6bc5 | |||
| 957c2b3bc1 | |||
| 494bde4e79 | |||
| 5e39136dff | |||
| 4b26a86a73 | |||
| 43a6e280c0 | |||
| 237a45b2ca | |||
| b161650ced | |||
| 24975eac31 | |||
| 5d1ff36565 | |||
| 628777900e | |||
| 12e87425dc | |||
| 873f049e20 | |||
| 2ea963ed03 | |||
| 1d1276d6dd | |||
| 83741724b0 | |||
| a4143cfe6d | |||
| 3d645ae2f4 | |||
| 5ba125c801 | |||
| badb392898 | |||
| c0e1ce8d86 | |||
| 0bc248c5e4 | |||
| 798dfb1727 | |||
| a451b987aa | |||
| f01074e5b8 | |||
| 0e12442a28 | |||
| a4e8489a34 | |||
| 276b6fbd22 | |||
| 52ab08c289 | |||
| 38236366cf | |||
| af3cc3c5dd | |||
| 35ed1f950c | |||
| c050ef945e | |||
| bed71fa3f8 | |||
| cf125daf5c | |||
| 9f425c2e8d | |||
| 0dc78241ac | |||
| 01e963e891 | |||
| b3731524ac | |||
| 67c7395ea7 | |||
| fddf36a920 | |||
| 4f561a8c0c | |||
| 778d6105c1 | |||
| 60c94dc9b6 | |||
| f71395e449 | |||
| 1abacca9bf | |||
| 40281d5403 | |||
| e0da489156 | |||
| 2dcf1350e7 | |||
| 1e280611ce | |||
| f1d107846f | |||
| cc951dcb53 | |||
| b5856a3706 | |||
| ed3479da9a | |||
| 5e15f421b7 | |||
| 0a9366ba6e | |||
| cf31435f39 | |||
| 9f58860842 | |||
| 875348383d | |||
| f79f190525 | |||
| 5e27a81412 | |||
| 0dcb009579 | |||
| 943f76804b | |||
| 8bbe6ae3ae | |||
| f0d85dd078 | |||
| f85dda1829 | |||
| 91e064cdf1 | |||
| fb4e53f6e3 | |||
| 03340ed091 | |||
| ed424fa0a2 | |||
| 406ab216d1 | |||
| 00d8a2064d | |||
| 38b920e393 | |||
| 1ed000c4d9 | |||
| d360958d10 | |||
| fcdb455d73 | |||
| 575639b721 | |||
| 492573f9fe | |||
| c5d30f8ee6 | |||
| 3c4791a622 | |||
| 803a5736c9 | |||
| 267ffbdf5f | |||
| 52028aa44c | |||
| c5248d53d6 | |||
| 2d2f0947ac | |||
| 4fa616a326 | |||
| 136713eec1 | |||
| 0fd75cb819 | |||
| ea52153969 | |||
| 3854781028 | |||
| ec2805f357 | |||
| b5cb3a65dd | |||
| c79cb3aa20 | |||
| 8bff119691 | |||
| 5e0b2c5b42 | |||
| 8908022b88 | |||
| b0dda0ed86 | |||
| 6ae72d4225 | |||
| 0a188a2d39 | |||
| 036abb28fe | |||
| a732767a28 | |||
| 32a1261d98 | |||
| 27c5af3bbc | |||
| 5872108da3 | |||
| 8f6c6b76de | |||
| 99db625c62 | |||
| fdf6a31cbd | |||
| 75f353d7e2 | |||
| 82f204fb44 | |||
| 8d4492ecfd | |||
| f8a53458d6 | |||
| 4229837170 | |||
| 4be2ae6c70 | |||
| dbdeba2fe0 | |||
| 7e34b61f37 | |||
| bf726ed2c7 | |||
| fa54a2affe | |||
| 62e1d0e554 | |||
| 9c823a038b | |||
| 1e6cd50f46 | |||
| 06716e4873 | |||
| 8e4a1e3ffa | |||
| 0abb3bd4c3 | |||
| 336574daed | |||
| 07938ba111 | |||
| e699eb6d25 | |||
| 3864549752 | |||
| 0b934cd0f6 | |||
| 5bac38a752 | |||
| 72c8d4d3dd | |||
| b8c6ea015e | |||
| ffe1beb7ae | |||
| 21c6dbfce0 | |||
| 70cbb8dc79 | |||
| 334f2a364d | |||
| b477354235 | |||
| 254c966159 | |||
| 7ee9b07d9c | |||
| 839b72469c | |||
| 874d76b343 | |||
| 7497e7aa0c | |||
| efa084fb0f | |||
| 48e4a27054 | |||
| 96cf6a790e | |||
| d7b54ff397 | |||
| 90ab065073 | |||
| b6f0784311 | |||
| e37ec654ee | |||
| b237d51276 | |||
| 155ea24008 | |||
| 8c8affc800 | |||
| 481062fca1 | |||
| ffcc5560dc | |||
| 09e146ef0b | |||
| 4c6b04ff69 | |||
| 9889b479d1 | |||
| 95dec00c76 | |||
| cff268926d | |||
| 6fa88f4e4a | |||
| ab8e6791fe | |||
| 13c45cc59a | |||
| 67c468884f | |||
| f028d44609 | |||
| 18b952e612 | |||
| 25178d8f50 | |||
| 1c0b7c00fd | |||
| 2439761529 | |||
| 8803dd5b65 | |||
| d15d04eae5 | |||
| bf40f74a4a | |||
| c0339c0f46 | |||
| b64bb166c0 | |||
| 31d30030dc | |||
| 556e111a98 | |||
| 70b0dd621b | |||
| f7d3212651 | |||
| 0a29f0cfa1 | |||
| 97153ad59d | |||
| bc8378fb60 | |||
| 3320cf8da8 | |||
| bb53bd3f27 | |||
| 73eed59fab | |||
| 91ede52634 | |||
| 93f13a98b2 | |||
| c87c5c9709 | |||
| b0c6c53430 | |||
| 94a5222390 | |||
| 98bb304060 | |||
| 08bfd923ea | |||
| ae28f04ce4 | |||
| 024a742f2a | |||
| df184f3e54 | |||
| 5542410afa | |||
| 99205cdc0f | |||
| 8c936af963 | |||
| 7fe751e74f | |||
| 6d551578c3 | |||
| 40c85fb607 | |||
| 743736b376 | |||
| 7fdb431d70 | |||
| ebcc3d8912 | |||
| 32e29a54c3 | |||
| 049733c4b6 | |||
| 420d58527d | |||
| bab779a34c | |||
| 45aa71b2b7 | |||
| 6dcfe2cad6 | |||
| f206047908 | |||
| 6ce979a7de | |||
| 97f97eb063 | |||
| f3db762e9f | |||
| f9f623dfa5 | |||
| ffa6bec3b4 | |||
| 4f78973751 | |||
| a8a7af4b74 | |||
| 45295c779c | |||
| a82376d1f5 | |||
| 75c6248264 | |||
| 9294ab4f97 | |||
| f01193e854 | |||
| d7375bc4c3 | |||
| 1a860c6ffd | |||
| 800ed3af7a | |||
| 9c8e79546c | |||
| 4c272aa536 | |||
| e184861822 | |||
| d40e19f08d | |||
| 817ee0721a | |||
| 22ec4afdab | |||
| 61626897e7 | |||
| 6fd3edbb8f | |||
| fc5b02ed5d | |||
| a06e752b76 | |||
| 3a947bf81b | |||
| 31121ca885 | |||
| 387b8c46ff | |||
| 66fda34b20 | |||
| 1542c5f4fe | |||
| 523fc7b8f9 | |||
| 73faf04ea1 | |||
| e10ddf9d2d | |||
| 641a7ea75d | |||
| e543d5c27f | |||
| 01c59ab0c6 | |||
| a4c64abed4 | |||
| 7df11a6f67 | |||
| 1bd6020163 | |||
| b3ac3131b5 | |||
| f522cb1db1 | |||
| d96a4853fe | |||
| 52a0447fea | |||
| e82e6d56f1 | |||
| 3967ef453d | |||
| 76f7751d5f | |||
| 8716ffc873 | |||
| b476e4cfb0 | |||
| 7ec77a10d3 | |||
| 55a9c5ef71 | |||
| 6d3ba31993 | |||
| d3f4a674aa | |||
| 599ab20ed0 | |||
| dcf33e125b | |||
| 01600b96a4 | |||
| 64bdc4c18c | |||
| 0889b8a7c5 | |||
| 1b2fee3ab8 | |||
| da7a4433c0 | |||
| 5e5d89cc92 | |||
| a3bee4baa9 | |||
| fab83ec399 | |||
| b740e36985 | |||
| 29693c6fe2 | |||
| 72638f40a6 | |||
| 8d29e83d90 | |||
| 53b325d34d | |||
| d31cf6e297 | |||
| e386a5d08b | |||
| d467ed9ece | |||
| 892a467d74 | |||
| 4366e71f34 | |||
| 7e9998b4fd | |||
| 79abe93139 | |||
| d69d4b3920 | |||
| 3300541181 | |||
| 3848059f19 | |||
| 30021d89cb | |||
| 29019724bd | |||
| ba7838c04e | |||
| af16c68e47 | |||
| bda5717051 | |||
| fac4973329 | |||
| 90cfaa4e82 | |||
| 443aa575df | |||
| 619771c3a3 | |||
| 18a56cfd52 | |||
| 55c39ff27c | |||
| 159c7a9a52 | |||
| af8edc335b | |||
| 4d3ea37bc3 | |||
| 226004da94 | |||
| 47b358351f | |||
| f5d77a1dfb | |||
| 9c9f0a20f9 | |||
| 6d9d410a70 | |||
| d8f3ad8d3f | |||
| a1b75b9746 | |||
| 80f3bfaece | |||
| 37b2d8a6ec | |||
| 777fea9cea | |||
| bbfdd37935 | |||
| 07484725a0 | |||
| 709b126a67 | |||
| 28e6302b3d | |||
| 27861e96f8 | |||
| e36312a3cb | |||
| 5b5dbdaa91 | |||
| 99dc97365f | |||
| aac2b9f987 | |||
| 067c275c46 | |||
| 58004d7c05 | |||
| aa0d9c5c13 | |||
| 9e46950e28 | |||
| a6551fc019 | |||
| a06ae40797 | |||
| 1db08438df | |||
| 89aa51ab61 | |||
| ddb7a92c15 | |||
| e273900e87 | |||
| d2d121d49f | |||
| 9963cf37b8 | |||
| 72300cc821 | |||
| 8168d9bb92 | |||
| 8f0151fed6 | |||
| d3c4928eda | |||
| 68f95cd80b | |||
| 42935c8238 | |||
| 118acf77b8 | |||
| 661964277f | |||
| 464dc23ff0 | |||
| 44dc2d06c6 | |||
| c00b592ed9 | |||
| e005826151 | |||
| a61b15cf6a | |||
| fe3a3e22f7 | |||
| 68cb4a6740 | |||
| 9f06bed34c | |||
| 3b1936ef48 | |||
| 5b3d26a90a | |||
| b381a61be8 | |||
| 1e2fa2068c | |||
| c604214bb9 | |||
| e738c9561a | |||
| 994d1c8ee5 | |||
| ce21800537 | |||
| d02cdd5471 | |||
| 7018e412d5 | |||
| 94f7505076 | |||
| b82ecf047a | |||
| f21b93403a | |||
| 59c88bc43b | |||
| 8e98c1b038 | |||
| 4d3570fe4c | |||
| 3706769c33 | |||
| ce91c34b21 | |||
| e37aa5e51a | |||
| 80af0f4539 | |||
| fc818f00f1 | |||
| a55d39b7d4 | |||
| 8e264359db | |||
| cbaeaa9f81 | |||
| 323c2285ce | |||
| 5b6d0ec337 | |||
| 2bbb0f5ec2 |
@@ -10,5 +10,6 @@ docs/build
|
||||
rns*.egg-info
|
||||
profile.data
|
||||
tests/rnsconfig/storage
|
||||
tests/rnsconfig/logfile*
|
||||
*.data
|
||||
*.result
|
||||
|
||||
+388
-1
@@ -1,3 +1,390 @@
|
||||
### 2023-11-14: RNS β 0.6.8
|
||||
|
||||
This maintenance release fixes a single bug.
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed packet receipts not being initialised in time for arriving proofs on fast interfaces
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
3ffb01f3f45e35105ea30e60e5e493ba50528df38b4ea62672c9e1c093073b1c rns-0.6.8-py3-none-any.whl
|
||||
de372814082ef7db59f4b2745b1f22b2ef9d97815190ec16c0596ba20406e0fb rnspure-0.6.8-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-11-06: RNS β 0.6.7
|
||||
|
||||
This maintenance release improves tranport performance and fixes a logging bug.
|
||||
|
||||
**Changes**
|
||||
- Improved local and remote transport performance by approximately 6x on faster links
|
||||
- Significantly decreased latency over faster links
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed logging an error message when local clients connect while shared instance is still starting up
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
c37dd1f59e037841f69ec518deecdae6719f978947de2473f04e7d95247805ac rns-0.6.7-py3-none-any.whl
|
||||
1e2dcb44ec7271a4d26180db138fc54dce6d0d3cf3f816432d4d6a4b1cf83868 rnspure-0.6.7-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-11-04: RNS β 0.6.6
|
||||
|
||||
This maintenance release improves transfers over unreliable links and fixes a bug in requests.
|
||||
|
||||
**Changes**
|
||||
- Improved reliability of resource transfers over very slow and unreliable links
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed a bug that could cause requests to timeout prematurely
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
b1127745750a43cd7389212d31aa09ccc735ab2d69e3b80bd28874f10082c322 rns-0.6.6-py3-none-any.whl
|
||||
bf5ba5da4f37b93c14817367952cda63787ec88bbe601e41c13fcbb3fc22b6b6 rnspure-0.6.6-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-11-02: RNS β 0.6.5
|
||||
|
||||
This release fixes a bug in path rediscovery for shared instance clients.
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed a bug in path rediscovery for shared instance clients
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
5d54a5cfebe907c759351357a8f7d771670c895ff57f1325bf7fec42bcb46ba3 rns-0.6.5-py3-none-any.whl
|
||||
accd2855e18ff06455b9454957388089e293073ec7093c64dee0dc7aa46ecb46 rnspure-0.6.5-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-11-02: RNS β 0.6.4
|
||||
|
||||
This release fixes a number of bugs that had crept in while adding the new ultra low bandwidth link timing and faster path rediscovery mechanisms.
|
||||
|
||||
**Changes**
|
||||
- Adjusted link timings for better support of very slow mediums
|
||||
- Adjusted bluetooth read timeouts to account for occasional high latency in congested 2.4GHz environments
|
||||
- Added a probe count option to the `rnprobe` utility.
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed a missing timeout calculation
|
||||
- Fixed a redundant path request on path rediscovery
|
||||
- Fixed missing path state resetting on stale path rediscovery
|
||||
- Fixed a bug that could cause an attribute to be uninitialised
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
566c725f68aa154eaca0880c894a39503027bf91714f17691e51d047800444c0 rns-0.6.4-py3-none-any.whl
|
||||
a3a447fd40bf02fdb982523de0e4e9933e8e4cd4d4bd478254ea7dcac29e3fc1 rnspure-0.6.4-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-10-31: RNS β 0.6.3
|
||||
|
||||
This release brings a series of under-the-hood reliability improvements and bugfixes. But most notably, Reticulum can now establish links over even ultra low bandwidth mediums, all the way down to 5 bits per second.
|
||||
|
||||
Thanks to @jschulthess, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Implemented link establishment on ultra low bandwidth links
|
||||
- Added link quality calculations to RNode interfaces
|
||||
- Added physical layer link stats to Link and Packet classes
|
||||
- Added userspace service documentation to the manual
|
||||
- Improved path rediscovery in quickly changing topographies
|
||||
- Improved shared interface reconnection on service restart
|
||||
- Improved exception handling on interface detachment
|
||||
- Updated formatted print functions
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed a missing USB command definition in the RNode interface driver
|
||||
- Fixed a bug in link error handling that could cause an interface to detach
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
1f54d4c6ff7ab7721089cbee6630783765f65efd51312879c0d3e5bee3ceab2f rns-0.6.3-py3-none-any.whl
|
||||
5a90840f0fc9f1a62a3c37b514fb6222fd701a30024275dae8bcc27e29d40f25 rnspure-0.6.3-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-10-07: RNS β 0.6.2
|
||||
|
||||
This maintenance release adds the ability to specify the shared instance RPC key in the Reticulum config file, making it possible to use all Reticulum functionality in the terminal on Android.
|
||||
|
||||
**Changes**
|
||||
- Added configuration option to specify shared instance RPC key
|
||||
- Reordered airtime stats in `rnstatus`
|
||||
- Updated log levels on Android
|
||||
|
||||
**Bugfixes**
|
||||
- Adding missing superclass init on Android interfaces
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
a9958ad90f34f344003e18077f7abd3fa85666a39dc0cae8580071820dee13f9 rns-0.6.2-py3-none-any.whl
|
||||
e68e8837d35d1a07a82c4b0e9db50ceace737a650e6e7e9ce2d9a013fd28f529 rnspure-0.6.2-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-10-01: RNS β 0.6.1
|
||||
|
||||
This release brings a number of bugfixes, along with useful new mechanisms for ensuring network stability under high, non-constructive and unusual announce load situation.
|
||||
|
||||
**Changes**
|
||||
- Added announce ingress rate control for new and unknown destinations
|
||||
- Added per-interface announce frequency monitoring to the transport engine
|
||||
- Added per-interface announce burst hold queues
|
||||
- Added announce frequency statistics to `rnstatus`
|
||||
- Added option to sort `rnstatus` output according to various metrics
|
||||
- Added timeout options to `rnprobe`
|
||||
- Added ability to drop all paths via a specific transport instance to `rnpath`
|
||||
- Added new options and features to documentation and manual
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed announce queue not clearing all announces with exceeded retry limit at the same time
|
||||
- Fixed a bug that caused local packet RSSI and SNR cache to get stuck
|
||||
- Fixed output formatting in `rncp`
|
||||
- Fixed `rnid` not allowing single-aspect destination names
|
||||
- Fixed a number of typos in the documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
461e5cafa7560dcd3ec047141d10f0f48f151c36e1af1d65ec6c65f732cea46a rns-0.6.1-py3-none-any.whl
|
||||
be6a4a6069f2d050e21582f2cf9d3bb59ed4040a0f07761a540bd752d90ea591 rnspure-0.6.1-py3-none-any.whl
|
||||
```
|
||||
|
||||
#### 2023-09-21: RNS β 0.6.0
|
||||
|
||||
This release brings a few performance improvements, additions to the included utilities, and fixes a number of bugs.
|
||||
|
||||
**Changes**
|
||||
- Added ability to run automatic probe responder on Transport Instances
|
||||
- Improved `rnprobe` utility
|
||||
- Improved AutoInterface peering on Android devices
|
||||
- Improved Transport performance
|
||||
- Improved path re-discovery when local nodes roam to other network segments
|
||||
- Updated various parts of the documentation
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed missing timeout check in `rncp`
|
||||
- Fixed missing link status check on `Identify()` call, which could lead to an unnecessary exception
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
88a26b1593e82a628dab96dbe8820548aea0159235f730fa992bf1833db59246 rns-0.6.0-py3-none-any.whl
|
||||
bcee416e4fb52346d01f6e0c46b1cebf84b127cc516603367fc2ae00a4149fa2 rnspure-0.6.0-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-09-19: RNS β 0.5.9
|
||||
|
||||
This release brings major efficiency improvements to `Channel` and `Buffer` classes, adds a range of usability improvements to the included utilities and fixes a number of bugs.
|
||||
|
||||
**Changes**
|
||||
- Improved `Channel` sequencing, retries and transfer efficiency
|
||||
- Added adaptive compression to `Buffer` class
|
||||
- Added `rnid` examples and documentation to manual
|
||||
- Added silent mode to `rncp`
|
||||
- Added remote fetch mode to `rncp`
|
||||
- Added allowed_identities file support to `rncp`
|
||||
- Added Transport Instance uptime to `rnstatus` output
|
||||
- Added channel CSMA parameter stats to RNode Interface `rnstatus` output
|
||||
- Added ability to set custom RNode OLED display address with rnodeconf
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed inadverdent AutoInterface multi-IF deque hit for resource transfer retries
|
||||
- Fixed invalid path for firmware hash generation while using extracted firmware to autoinstall in `rnodeconf`
|
||||
- Fixed various minor missing error checks
|
||||
- Fixed `rnid` status output bug
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
207ab20bd68bab16b417fbd41a4ecdbcf1e2f6fa553d48df6c8fc181b6e84dac rns-0.5.9-py3-none-any.whl
|
||||
93f0965567dfc2c43f3d703481fe1a7d7b1b8d0b3837ad41c37f28a8af5c1acc rnspure-0.5.9-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-09-14: RNS β 0.5.8
|
||||
|
||||
This maintenance release contains a number of usability improvements to Reticulum and related tools.
|
||||
|
||||
**Changes**
|
||||
- Various documentation updates
|
||||
- Improved path-resolution in mixed networks with roaming-mode nodes
|
||||
- Added channel load and airtime stats to `rnstatus` output
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
27ba5cdc4724fc8c7211c3b504f097f6adf47f7b80775e6297e4c4e621ef6348 rns-0.5.8-py3-none-any.whl
|
||||
1ea1c949763c9478ec48f064f7f7864d9f859101ab91b44400879371f490800f rnspure-0.5.8-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-08-14: RNS β 0.5.7
|
||||
|
||||
This maintenance release contains a number of bugfixes and quality improvements to Reticulum and related tools.
|
||||
|
||||
**Changes**
|
||||
- Added bytes input to destination hash convenience functions
|
||||
- Fixed possible invalid comparison in link watchdog job
|
||||
- Add option to `rnodeconf` to set baud rate when flashing
|
||||
- Added better explanation in `rnodeconf` when flashing fails
|
||||
- Fixed EEPROM dump directory in `rnodeconf`
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
867fbb5c73c2a49a75e1f8f3e9f376b507b683328e26c64d4387acd0cc1dbbc7 rns-0.5.7-py3-none-any.whl
|
||||
7bab2865264b32208e023b5c4bbe88c37f51e3176ca4a8cf332d95f59a6d7f2c rnspure-0.5.7-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-07-09: RNS β 0.5.6
|
||||
|
||||
This maintenance release contains a few bugfixes.
|
||||
|
||||
**Changes**
|
||||
- Fixed an issue in `rnodeconf` that prevented Heltec LoRa32 v2 boards from being flashed.
|
||||
- Fixed a typo in the `rnid` utility.
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
255a5b4bac28326c6b2cc85f43b26dcb0606404a4abd2dfa8244937155838973 rns-0.5.6-py3-none-any.whl
|
||||
1510b6da4641ceaa4c599a142e498c7e2c1ae12035868f9db1c111e5600161e9 rnspure-0.5.6-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-06-13: RNS β 0.5.5
|
||||
|
||||
This maintenance release brings a single bugfix.
|
||||
|
||||
**Changes**
|
||||
- Fixed a race condition for link initiators on timed out link establishments.
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
4ae61d28bf981a7cb853c179e9de3b56b350d2dc984fb671a21d38c4ce5b449e rns-0.5.5-py3-none-any.whl
|
||||
ed417cbd3c90e9f1b68565a3411ca5c9bc936b495300fd1ace3c4a6414aabd5a rnspure-0.5.5-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-05-19: RNS β 0.5.4
|
||||
|
||||
This maintenance release brings a single bugfix.
|
||||
|
||||
**Changes**
|
||||
- Fixed a potential race condition when timed-out link receives a late establishment proof a few milliseconds after it has timed out.
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
71b42fe737da97a4b63bb227c29bb67854a7f003c9585f085b0ff68c8f460815 rns-0.5.4-py3-none-any.whl
|
||||
af6949d581445444f57cfca75756200e7c509a6fc66483d859716ce6a06064db rnspure-0.5.4-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-05-19: RNS β 0.5.3
|
||||
|
||||
This maintenance release brings a single, but important bugfix.
|
||||
|
||||
**Changes**
|
||||
- Fixed a bug that could cause data corruption to occur over when using `Buffer` instances.
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
f23c8d655c9e80a12a6728495aec56f19f27184d3d8e6b6ed6184b0e89d4be35 rns-0.5.3-py3-none-any.whl
|
||||
2c692a2153bb766a9dc2391340a06f429c13a75b86b746b69c6fcd5a4fe5ee33 rnspure-0.5.3-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-05-12: RNS β 0.5.2
|
||||
|
||||
This maintenance release brings a number of bugfixes and improvements.
|
||||
|
||||
**Important!** This release breaks backwards compatibility with `Channel` and `Buffer` for all previous releases, due to the addition of compression and windowing.
|
||||
|
||||
**Changes**
|
||||
- Added ability to trust external signing keys to `rnodeconf`
|
||||
- Added basic windowing to `Channel` and `Buffer`, improving performance over faster links
|
||||
- Added per-packet compression to `Channel`
|
||||
- Added automatic multi-interface duplicate deque to AutoInterface
|
||||
- Fixed received link packet proofs not resetting link watchdog stale timer
|
||||
- Fixed a missing exception isolation of packet delivery callbacks
|
||||
- Fixed resent packets not getting repacked
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
f3b1e9cf39420ad74c2b5c81ad339fd2a548320c9f6925bad9b614feb4c9b9d7 rns-0.5.2-py3-none-any.whl
|
||||
8463f7365f179d02e7e4d4fe4afc69da4218ce40107305dfd06b9e6b29513e0f rnspure-0.5.2-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-05-05: RNS β 0.5.1
|
||||
|
||||
This maintenance release brings a number of bugfixes and improvements. Thanks to @VioletEternity, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Removed dependency on netifaces module
|
||||
- Added ability to configure RNode display intensity to rnodeconf
|
||||
- Added preliminary rnodeconf flasher/autoinstaller support for T3 v1.0 boards
|
||||
- Fixed a bug that caused AutoInterface discovery scopes to fail
|
||||
- Fixed rnodeconf firmware extraction for unverifiable devices
|
||||
- Improved setting rnsd verbosity from command line
|
||||
- Improved support for shared instances on Windows
|
||||
- Improved rnodeconf support on Windows
|
||||
- Improved rnodeconf zip-file handling
|
||||
- Fixed a potentail race condition in announce queue handling for AutoInterface
|
||||
- Various minor bugfixes
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
01d76e03f93e427d9c0b95ab5d07e84ed39047e912b8afa6d619a65ac6b5e05b rns-0.5.1-py3-none-any.whl
|
||||
2cfe431bec1160410b80bbcbf87eb2ab0d5abe5c6546f41eaf3f0f5faf9b2140 rnspure-0.5.1-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-03-08: RNS β 0.5.0
|
||||
|
||||
This release brings two major new additions to the Reticulum API: The Channel and Buffer classes, that provides reliable delivery, and streams over Reticulum. Thanks to @acehoss, @erethon, @gdt and @faragher, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added the Buffer class to the API
|
||||
- Added the Channel class to the API
|
||||
- Improved error messages for offline RNode flashing
|
||||
- Improved RNode reconnection when serial device disappears
|
||||
- Fixed embedded scope identifier handling for AutoInterface on BSD
|
||||
- Fixed AutoInterface not ignoring lo0 on BSD
|
||||
- Fixed a bug causing JSON output from rnstatus to fail
|
||||
- Fixed invalid installation of test suite into root module path
|
||||
- Added EPUB version of the documentation
|
||||
- Updated documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
0aaf8c0b0b58f07071de5ecd432f4d9cc176b9614419c828b81ad71aa7151624 rns-0.5.0-py3-none-any.whl
|
||||
f310a5192c2df7665339c5998ae13815a647283af75b95ad7acbee8c20989954 rnspure-0.5.0-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-02-17: RNS β 0.4.9
|
||||
|
||||
This maintenance release contains a number of bugfixes and minor improvements, along with a few additions to the API.
|
||||
|
||||
**Changes**
|
||||
- Added JSON output mode to rnstatus
|
||||
- Added Link ID to response_generator callback
|
||||
- Added Link establishment rate calculation
|
||||
- Added get_establishment_rate call to Link API
|
||||
- Fixed a number of typos in programs and documentation
|
||||
- Fixed some broken links in documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
b44eaed796dcd194bec7a541aaeeb1685b07b2ffce068ca268841e6a8661717f rns-0.4.9-py3-none-any.whl
|
||||
a15f965a27d208493485724486eb6bc6268d699f2a22ae4fb816bb9b979330fc rnspure-0.4.9-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-02-04: RNS β 0.4.8
|
||||
|
||||
This release introduces the useful `rnid` utility, which makes it possible to use Reticulum Identities for offline file encryption, decryption, signing and validation. The IFAC system has also been significantly improved, and several outdated parts of the documentation was updated and fixed. Thanks to @Erethon and @jooray who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added header and payload masking to the IFAC system
|
||||
- Added `rnid` utility for encrypting, decrypting, signing and validating with Reticulum Identities
|
||||
- Added Bluetooth pairing PIN output to `rnodeconf` utility
|
||||
- Fixed a bug in announce callback handling
|
||||
- Fixed a inconsistency in header flag handling since IFACs were introduced
|
||||
- Updated documentation and manual
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
fbbd55ee43a68c18491f5deabed51085c46fadca7e1bda823ad455c2f7c95a51 rns-0.4.8-py3-none-any.whl
|
||||
335b0d5dd1d2aacd0d8810191aa09567ecf5d3aa990c446f3e3b1bbf7fce1387 rnspure-0.4.8-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-01-14: RNS β 0.4.7
|
||||
|
||||
This maintenance release adds support for using the `rnodeconf` utility to replicate RNode devices, and bootstrap device creation using only tools and software packages obtained from an RNode Bootstrap Console.
|
||||
@@ -662,4 +1049,4 @@ This was the first publicly available pre-release alpha of Reticulum.
|
||||
|
||||
### 2016-05-29: Inintial Repository Commit
|
||||
|
||||
The first commit to the Reticulum reference implementation was 9a9630cfd29e11ace3f12716ddb4dff0e5419b4b, which occurred on Sunday, the 22nd of May 2016.
|
||||
The first commit to the Reticulum reference implementation was 9a9630cfd29e11ace3f12716ddb4dff0e5419b4b, which occurred on Sunday, the 29th of May 2016.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Contributing to Reticulum
|
||||
|
||||
Welcome, and thank you for your interest in contributing to Reticulum!
|
||||
|
||||
Apart from writing code, there are many ways in which you can contribute. Before getting started, please read these guidelines.
|
||||
|
||||
## Asking Questions
|
||||
|
||||
If you want to ask a question, do not open an issue.
|
||||
|
||||
Instead, ask away on the [discussions](https://github.com/markqvist/Reticulum/discussions) or on the [Reticulum Matrix channel](https://unsigned.io/contact.html#reticulum:matrix.org) at `#reticulum:matrix.org`
|
||||
|
||||
## Providing Feedback
|
||||
|
||||
Likewise, feedback, ideas and feature requests are a very welcome way to contribute, and should also be posted on the [discussions](https://github.com/markqvist/Reticulum/discussions), or on the [Reticulum Matrix channel](https://unsigned.io/contact.html#reticulum:matrix.org) at `#reticulum:matrix.org`
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If you have found a bug or issue in Reticulum, please report it on the [issue tracker](https://github.com/markqvist/Reticulum/issues).
|
||||
|
||||
## Writing Code
|
||||
|
||||
If you are interested in contributing code, fixing open issues or adding features, please coordinate the effort with the maintainer or one of the main developers first, to ensure your efforts are in alignment with the [Roadmap](./Roadmap.md) and current development focus.
|
||||
@@ -32,7 +32,7 @@ def program_setup(configpath):
|
||||
# Destinations are endpoints in Reticulum, that can be addressed
|
||||
# and communicated with. Destinations can also announce their
|
||||
# existence, which will let the network know they are reachable
|
||||
# and autoomatically create paths to them, from anywhere else
|
||||
# and automatically create paths to them, from anywhere else
|
||||
# in the network.
|
||||
destination_1 = RNS.Destination(
|
||||
identity,
|
||||
@@ -53,7 +53,7 @@ def program_setup(configpath):
|
||||
)
|
||||
|
||||
# We configure the destinations to automatically prove all
|
||||
# packets adressed to it. By doing this, RNS will automatically
|
||||
# packets addressed to it. By doing this, RNS will automatically
|
||||
# generate a proof for each incoming packet and transmit it
|
||||
# back to the sender of that packet. This will let anyone that
|
||||
# tries to communicate with the destination know whether their
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates how to set up a link to #
|
||||
# a destination, and pass binary data over it using a #
|
||||
# channel buffer. #
|
||||
##########################################################
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import RNS
|
||||
from RNS.vendor import umsgpack
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create. Since this echo example
|
||||
# is part of a range of example utilities, we'll put
|
||||
# them all within the app namespace "example_utilities"
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the latest client link that connected
|
||||
latest_client_link = None
|
||||
|
||||
# A reference to the latest buffer object
|
||||
latest_buffer = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a server
|
||||
def server(configpath):
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Randomly create a new identity for our example
|
||||
server_identity = RNS.Identity()
|
||||
|
||||
# We create a destination that clients can connect to. We
|
||||
# want clients to create links to this destination, so we
|
||||
# need to create a "single" destination type.
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.IN,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"bufferexample"
|
||||
)
|
||||
|
||||
# We configure a function that will get called every time
|
||||
# a new client creates a link to this destination.
|
||||
server_destination.set_link_established_callback(client_connected)
|
||||
|
||||
# Everything's ready!
|
||||
# Let's Wait for client requests or user input
|
||||
server_loop(server_destination)
|
||||
|
||||
def server_loop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Link buffer example "+
|
||||
RNS.prettyhexrep(destination.hash)+
|
||||
" running, waiting for a connection."
|
||||
)
|
||||
|
||||
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
|
||||
|
||||
# We enter a loop that runs until the users exits.
|
||||
# If the user hits enter, we will announce our server
|
||||
# destination on the network, which will let clients
|
||||
# know how to create messages directed towards it.
|
||||
while True:
|
||||
entered = input()
|
||||
destination.announce()
|
||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||
|
||||
# When a client establishes a link to our server
|
||||
# destination, this function will be called with
|
||||
# a reference to the link.
|
||||
def client_connected(link):
|
||||
global latest_client_link, latest_buffer
|
||||
latest_client_link = link
|
||||
|
||||
RNS.log("Client connected")
|
||||
link.set_link_closed_callback(client_disconnected)
|
||||
|
||||
# If a new connection is received, the old reader
|
||||
# needs to be disconnected.
|
||||
if latest_buffer:
|
||||
latest_buffer.close()
|
||||
|
||||
|
||||
# Create buffer objects.
|
||||
# The stream_id parameter to these functions is
|
||||
# a bit like a file descriptor, except that it
|
||||
# is unique to the *receiver*.
|
||||
#
|
||||
# In this example, both the reader and the writer
|
||||
# use stream_id = 0, but there are actually two
|
||||
# separate unidirectional streams flowing in
|
||||
# opposite directions.
|
||||
#
|
||||
channel = link.get_channel()
|
||||
latest_buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, server_buffer_ready)
|
||||
|
||||
def client_disconnected(link):
|
||||
RNS.log("Client disconnected")
|
||||
|
||||
def server_buffer_ready(ready_bytes: int):
|
||||
"""
|
||||
Callback from buffer when buffer has data available
|
||||
|
||||
:param ready_bytes: The number of bytes ready to read
|
||||
"""
|
||||
global latest_buffer
|
||||
|
||||
data = latest_buffer.read(ready_bytes)
|
||||
data = data.decode("utf-8")
|
||||
|
||||
RNS.log("Received data over the buffer: " + data)
|
||||
|
||||
reply_message = "I received \""+data+"\" over the buffer"
|
||||
reply_message = reply_message.encode("utf-8")
|
||||
latest_buffer.write(reply_message)
|
||||
latest_buffer.flush()
|
||||
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the server link
|
||||
server_link = None
|
||||
|
||||
# A reference to the buffer object, needed to share the
|
||||
# object from the link connected callback to the client
|
||||
# loop.
|
||||
buffer = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a client
|
||||
def client(destination_hexhash, configpath):
|
||||
# We need a binary representation of the destination
|
||||
# hash that was entered on the command line
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(destination_hexhash) != dest_len:
|
||||
raise ValueError(
|
||||
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
|
||||
)
|
||||
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except:
|
||||
RNS.log("Invalid destination entered. Check your input!\n")
|
||||
exit()
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Check if we know a path to the destination
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
time.sleep(0.1)
|
||||
|
||||
# Recall the server identity
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
# Inform the user that we'll begin connecting
|
||||
RNS.log("Establishing link with server...")
|
||||
|
||||
# When the server identity is known, we set
|
||||
# up a destination
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"bufferexample"
|
||||
)
|
||||
|
||||
# And create a link
|
||||
link = RNS.Link(server_destination)
|
||||
|
||||
# We'll also set up functions to inform the
|
||||
# user when the link is established or closed
|
||||
link.set_link_established_callback(link_established)
|
||||
link.set_link_closed_callback(link_closed)
|
||||
|
||||
# Everything is set up, so let's enter a loop
|
||||
# for the user to interact with the example
|
||||
client_loop()
|
||||
|
||||
def client_loop():
|
||||
global server_link
|
||||
|
||||
# Wait for the link to become active
|
||||
while not server_link:
|
||||
time.sleep(0.1)
|
||||
|
||||
should_quit = False
|
||||
while not should_quit:
|
||||
try:
|
||||
print("> ", end=" ")
|
||||
text = input()
|
||||
|
||||
# Check if we should quit the example
|
||||
if text == "quit" or text == "q" or text == "exit":
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
else:
|
||||
# Otherwise, encode the text and write it to the buffer.
|
||||
text = text.encode("utf-8")
|
||||
buffer.write(text)
|
||||
# Flush the buffer to force the data to be sent.
|
||||
buffer.flush()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while sending data over the link buffer: "+str(e))
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
# This function is called when a link
|
||||
# has been established with the server
|
||||
def link_established(link):
|
||||
# We store a reference to the link
|
||||
# instance for later use
|
||||
global server_link, buffer
|
||||
server_link = link
|
||||
|
||||
# Create buffer, see server_client_connected() for
|
||||
# more detail about setting up the buffer.
|
||||
channel = link.get_channel()
|
||||
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, client_buffer_ready)
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
|
||||
|
||||
# When a link is closed, we'll inform the
|
||||
# user, and exit the program
|
||||
def link_closed(link):
|
||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
RNS.log("The link timed out, exiting now")
|
||||
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
||||
RNS.log("The link was closed by the server, exiting now")
|
||||
else:
|
||||
RNS.log("Link closed, exiting now")
|
||||
|
||||
RNS.Reticulum.exit_handler()
|
||||
time.sleep(1.5)
|
||||
os._exit(0)
|
||||
|
||||
# When the buffer has new data, read it and write it to the terminal.
|
||||
def client_buffer_ready(ready_bytes: int):
|
||||
global buffer
|
||||
data = buffer.read(ready_bytes)
|
||||
RNS.log("Received data over the link buffer: " + data.decode("utf-8"))
|
||||
print("> ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Program Startup #####################################
|
||||
##########################################################
|
||||
|
||||
# This part of the program runs at startup,
|
||||
# and parses input of from the user, and then
|
||||
# starts up the desired program mode.
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Simple buffer example")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming link requests from clients"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the server destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if args.server:
|
||||
server(configarg)
|
||||
else:
|
||||
if (args.destination == None):
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
client(args.destination, configarg)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
@@ -0,0 +1,390 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates how to set up a link to #
|
||||
# a destination, and pass structured messages over it #
|
||||
# using a channel. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import RNS
|
||||
from RNS.vendor import umsgpack
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create. Since this echo example
|
||||
# is part of a range of example utilities, we'll put
|
||||
# them all within the app namespace "example_utilities"
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
##########################################################
|
||||
#### Shared Objects ######################################
|
||||
##########################################################
|
||||
|
||||
# Channel data must be structured in a subclass of
|
||||
# MessageBase. This ensures that the channel will be able
|
||||
# to serialize and deserialize the object and multiplex it
|
||||
# with other objects. Both ends of a link will need the
|
||||
# same object definitions to be able to communicate over
|
||||
# a channel.
|
||||
#
|
||||
# Note: The objects we wish to use over the channel must
|
||||
# be registered with the channel, and each link has a
|
||||
# different channel instance. See the client_connected
|
||||
# and link_established functions in this example to see
|
||||
# how message types are registered.
|
||||
|
||||
# Let's make a simple message class called StringMessage
|
||||
# that will convey a string with a timestamp.
|
||||
|
||||
class StringMessage(RNS.MessageBase):
|
||||
# The MSGTYPE class variable needs to be assigned a
|
||||
# 2 byte integer value. This identifier allows the
|
||||
# channel to look up your message's constructor when a
|
||||
# message arrives over the channel.
|
||||
#
|
||||
# MSGTYPE must be unique across all message types we
|
||||
# register with the channel. MSGTYPEs >= 0xf000 are
|
||||
# reserved for the system.
|
||||
MSGTYPE = 0x0101
|
||||
|
||||
# The constructor of our object must be callable with
|
||||
# no arguments. We can have parameters, but they must
|
||||
# have a default assignment.
|
||||
#
|
||||
# This is needed so the channel can create an empty
|
||||
# version of our message into which the incoming
|
||||
# message can be unpacked.
|
||||
def __init__(self, data=None):
|
||||
self.data = data
|
||||
self.timestamp = datetime.now()
|
||||
|
||||
# Finally, our message needs to implement functions
|
||||
# the channel can call to pack and unpack our message
|
||||
# to/from the raw packet payload. We'll use the
|
||||
# umsgpack package bundled with RNS. We could also use
|
||||
# the struct package bundled with Python if we wanted
|
||||
# more control over the structure of the packed bytes.
|
||||
#
|
||||
# Also note that packed message objects must fit
|
||||
# entirely in one packet. The number of bytes
|
||||
# available for message payloads can be queried from
|
||||
# the channel using the Channel.MDU property. The
|
||||
# channel MDU is slightly less than the link MDU due
|
||||
# to encoding the message header.
|
||||
|
||||
# The pack function encodes the message contents into
|
||||
# a byte stream.
|
||||
def pack(self) -> bytes:
|
||||
return umsgpack.packb((self.data, self.timestamp))
|
||||
|
||||
# And the unpack function decodes a byte stream into
|
||||
# the message contents.
|
||||
def unpack(self, raw):
|
||||
self.data, self.timestamp = umsgpack.unpackb(raw)
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the latest client link that connected
|
||||
latest_client_link = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a server
|
||||
def server(configpath):
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Randomly create a new identity for our link example
|
||||
server_identity = RNS.Identity()
|
||||
|
||||
# We create a destination that clients can connect to. We
|
||||
# want clients to create links to this destination, so we
|
||||
# need to create a "single" destination type.
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.IN,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"channelexample"
|
||||
)
|
||||
|
||||
# We configure a function that will get called every time
|
||||
# a new client creates a link to this destination.
|
||||
server_destination.set_link_established_callback(client_connected)
|
||||
|
||||
# Everything's ready!
|
||||
# Let's Wait for client requests or user input
|
||||
server_loop(server_destination)
|
||||
|
||||
def server_loop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Link example "+
|
||||
RNS.prettyhexrep(destination.hash)+
|
||||
" running, waiting for a connection."
|
||||
)
|
||||
|
||||
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
|
||||
|
||||
# We enter a loop that runs until the users exits.
|
||||
# If the user hits enter, we will announce our server
|
||||
# destination on the network, which will let clients
|
||||
# know how to create messages directed towards it.
|
||||
while True:
|
||||
entered = input()
|
||||
destination.announce()
|
||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||
|
||||
# When a client establishes a link to our server
|
||||
# destination, this function will be called with
|
||||
# a reference to the link.
|
||||
def client_connected(link):
|
||||
global latest_client_link
|
||||
latest_client_link = link
|
||||
|
||||
RNS.log("Client connected")
|
||||
link.set_link_closed_callback(client_disconnected)
|
||||
|
||||
# Register message types and add callback to channel
|
||||
channel = link.get_channel()
|
||||
channel.register_message_type(StringMessage)
|
||||
channel.add_message_handler(server_message_received)
|
||||
|
||||
def client_disconnected(link):
|
||||
RNS.log("Client disconnected")
|
||||
|
||||
def server_message_received(message):
|
||||
"""
|
||||
A message handler
|
||||
@param message: An instance of a subclass of MessageBase
|
||||
@return: True if message was handled
|
||||
"""
|
||||
global latest_client_link
|
||||
# When a message is received over any active link,
|
||||
# the replies will all be directed to the last client
|
||||
# that connected.
|
||||
|
||||
# In a message handler, any deserializable message
|
||||
# that arrives over the link's channel will be passed
|
||||
# to all message handlers, unless a preceding handler indicates it
|
||||
# has handled the message.
|
||||
#
|
||||
#
|
||||
if isinstance(message, StringMessage):
|
||||
RNS.log("Received data on the link: " + message.data + " (message created at " + str(message.timestamp) + ")")
|
||||
|
||||
reply_message = StringMessage("I received \""+message.data+"\" over the link")
|
||||
latest_client_link.get_channel().send(reply_message)
|
||||
|
||||
# Incoming messages are sent to each message
|
||||
# handler added to the channel, in the order they
|
||||
# were added.
|
||||
# If any message handler returns True, the message
|
||||
# is considered handled and any subsequent
|
||||
# handlers are skipped.
|
||||
return True
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the server link
|
||||
server_link = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a client
|
||||
def client(destination_hexhash, configpath):
|
||||
# We need a binary representation of the destination
|
||||
# hash that was entered on the command line
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(destination_hexhash) != dest_len:
|
||||
raise ValueError(
|
||||
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
|
||||
)
|
||||
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except:
|
||||
RNS.log("Invalid destination entered. Check your input!\n")
|
||||
exit()
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Check if we know a path to the destination
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
time.sleep(0.1)
|
||||
|
||||
# Recall the server identity
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
# Inform the user that we'll begin connecting
|
||||
RNS.log("Establishing link with server...")
|
||||
|
||||
# When the server identity is known, we set
|
||||
# up a destination
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"channelexample"
|
||||
)
|
||||
|
||||
# And create a link
|
||||
link = RNS.Link(server_destination)
|
||||
|
||||
# We'll also set up functions to inform the
|
||||
# user when the link is established or closed
|
||||
link.set_link_established_callback(link_established)
|
||||
link.set_link_closed_callback(link_closed)
|
||||
|
||||
# Everything is set up, so let's enter a loop
|
||||
# for the user to interact with the example
|
||||
client_loop()
|
||||
|
||||
def client_loop():
|
||||
global server_link
|
||||
|
||||
# Wait for the link to become active
|
||||
while not server_link:
|
||||
time.sleep(0.1)
|
||||
|
||||
should_quit = False
|
||||
while not should_quit:
|
||||
try:
|
||||
print("> ", end=" ")
|
||||
text = input()
|
||||
|
||||
# Check if we should quit the example
|
||||
if text == "quit" or text == "q" or text == "exit":
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
# If not, send the entered text over the link
|
||||
if text != "":
|
||||
message = StringMessage(text)
|
||||
packed_size = len(message.pack())
|
||||
channel = server_link.get_channel()
|
||||
if channel.is_ready_to_send():
|
||||
if packed_size <= channel.MDU:
|
||||
channel.send(message)
|
||||
else:
|
||||
RNS.log(
|
||||
"Cannot send this packet, the data size of "+
|
||||
str(packed_size)+" bytes exceeds the link packet MDU of "+
|
||||
str(channel.MDU)+" bytes",
|
||||
RNS.LOG_ERROR
|
||||
)
|
||||
else:
|
||||
RNS.log("Channel is not ready to send, please wait for " +
|
||||
"pending messages to complete.", RNS.LOG_ERROR)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while sending data over the link: "+str(e))
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
# This function is called when a link
|
||||
# has been established with the server
|
||||
def link_established(link):
|
||||
# We store a reference to the link
|
||||
# instance for later use
|
||||
global server_link
|
||||
server_link = link
|
||||
|
||||
# Register messages and add handler to channel
|
||||
channel = link.get_channel()
|
||||
channel.register_message_type(StringMessage)
|
||||
channel.add_message_handler(client_message_received)
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
|
||||
|
||||
# When a link is closed, we'll inform the
|
||||
# user, and exit the program
|
||||
def link_closed(link):
|
||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
RNS.log("The link timed out, exiting now")
|
||||
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
||||
RNS.log("The link was closed by the server, exiting now")
|
||||
else:
|
||||
RNS.log("Link closed, exiting now")
|
||||
|
||||
RNS.Reticulum.exit_handler()
|
||||
time.sleep(1.5)
|
||||
os._exit(0)
|
||||
|
||||
# When a packet is received over the channel, we
|
||||
# simply print out the data.
|
||||
def client_message_received(message):
|
||||
if isinstance(message, StringMessage):
|
||||
RNS.log("Received data on the link: " + message.data + " (message created at " + str(message.timestamp) + ")")
|
||||
print("> ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Program Startup #####################################
|
||||
##########################################################
|
||||
|
||||
# This part of the program runs at startup,
|
||||
# and parses input of from the user, and then
|
||||
# starts up the desired program mode.
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Simple channel example")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming link requests from clients"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the server destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if args.server:
|
||||
server(configarg)
|
||||
else:
|
||||
if (args.destination == None):
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
client(args.destination, configarg)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
+2
-1
@@ -46,7 +46,7 @@ def server(configpath):
|
||||
)
|
||||
|
||||
# We configure the destination to automatically prove all
|
||||
# packets adressed to it. By doing this, RNS will automatically
|
||||
# packets addressed to it. By doing this, RNS will automatically
|
||||
# generate a proof for each incoming packet and transmit it
|
||||
# back to the sender of that packet.
|
||||
echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
||||
@@ -210,6 +210,7 @@ def client(destination_hexhash, configpath, timeout=None):
|
||||
# If we do not know this destination, tell the
|
||||
# user to wait for an announce to arrive.
|
||||
RNS.log("Destination is not yet known. Requesting path...")
|
||||
RNS.log("Hit enter to manually retry once an announce is received.")
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
|
||||
# This function is called when our reply destination
|
||||
|
||||
@@ -449,8 +449,7 @@ def link_established(link):
|
||||
# And set up a small job to check for
|
||||
# a potential timeout in receiving the
|
||||
# file list
|
||||
thread = threading.Thread(target=filelist_timeout_job)
|
||||
thread.setDaemon(True)
|
||||
thread = threading.Thread(target=filelist_timeout_job, daemon=True)
|
||||
thread.start()
|
||||
|
||||
# This job just sleeps for the specified
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ def program_setup(configpath):
|
||||
# Destinations are endpoints in Reticulum, that can be addressed
|
||||
# and communicated with. Destinations can also announce their
|
||||
# existence, which will let the network know they are reachable
|
||||
# and autoomatically create paths to them, from anywhere else
|
||||
# and automatically create paths to them, from anywhere else
|
||||
# in the network.
|
||||
destination = RNS.Destination(
|
||||
identity,
|
||||
@@ -36,7 +36,7 @@ def program_setup(configpath):
|
||||
)
|
||||
|
||||
# We configure the destination to automatically prove all
|
||||
# packets adressed to it. By doing this, RNS will automatically
|
||||
# packets addressed to it. By doing this, RNS will automatically
|
||||
# generate a proof for each incoming packet and transmit it
|
||||
# back to the sender of that packet. This will let anyone that
|
||||
# tries to communicate with the destination know whether their
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ APP_NAME = "example_utilities"
|
||||
# A reference to the latest client link that connected
|
||||
latest_client_link = None
|
||||
|
||||
def random_text_generator(path, data, request_id, remote_identity, requested_at):
|
||||
RNS.log("Generating response to request "+RNS.prettyhexrep(request_id))
|
||||
def random_text_generator(path, data, request_id, link_id, remote_identity, requested_at):
|
||||
RNS.log("Generating response to request "+RNS.prettyhexrep(request_id)+" on link "+RNS.prettyhexrep(link_id))
|
||||
texts = ["They looked up", "On each full moon", "Becky was upset", "I’ll stay away from it", "The pet shop stocks everything"]
|
||||
return texts[random.randint(0, len(texts)-1)]
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ documentation:
|
||||
make -C docs html
|
||||
|
||||
manual:
|
||||
make -C docs latexpdf
|
||||
make -C docs latexpdf epub
|
||||
|
||||
release: test remove_symlinks build_wheel build_pure_wheel documentation manual create_symlinks
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ userland, and can run on practically any system that runs Python 3.
|
||||
## Read The Manual
|
||||
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
You can also [download the Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf)
|
||||
You can also download the [Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf) or [as an e-book in EPUB format](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.epub).
|
||||
|
||||
For more info, see [reticulum.network](https://reticulum.network/)
|
||||
|
||||
@@ -45,7 +45,7 @@ For more info, see [reticulum.network](https://reticulum.network/)
|
||||
- Initiator anonymity, communicate without revealing your identity
|
||||
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
- Forward Secrecy with ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for on-the-wire / over-the-air encryption
|
||||
- Reticulum uses the following format for encrypted tokens:
|
||||
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
- AES-128 in CBC mode with PKCS7 padding
|
||||
- HMAC using SHA256 for authentication
|
||||
@@ -59,11 +59,13 @@ For more info, see [reticulum.network](https://reticulum.network/)
|
||||
- The API is very easy to use, and provides transfer progress
|
||||
- Lightweight, flexible and expandable Request/Response mechanism
|
||||
- Efficient link establishment
|
||||
- Total bandwidth cost of setting up an encrypted link is 3 packets totaling 297 bytes
|
||||
- Total cost of setting up an encrypted and verified link is only 3 packets, totalling 297 bytes
|
||||
- Low cost of keeping links open at only 0.44 bits per second
|
||||
- Reliable sequential delivery with Channel and Buffer mechanisms
|
||||
|
||||
## Roadmap
|
||||
While Reticulum is already a fully featured and functional networking stack, many improvements and additions are actively being worked on, and planned for the future.
|
||||
While Reticulum is already a fully featured and functional networking stack,
|
||||
many improvements and additions are actively being worked on, and planned for the future.
|
||||
|
||||
To learn more about the direction and future of Reticulum, please see the [Development Roadmap](./Roadmap.md).
|
||||
|
||||
@@ -71,13 +73,14 @@ To learn more about the direction and future of Reticulum, please see the [Devel
|
||||
If you want to quickly get an idea of what Reticulum can do, take a look at the
|
||||
following resources.
|
||||
|
||||
- You can use the [rnsh](https://github.com/acehoss/rnsh) program to establish remote shell sessions over Reticulum.
|
||||
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
|
||||
- The Android, Linux and macOS app [Sideband](https://github.com/markqvist/Sideband) has a graphical interface and focuses on ease of use.
|
||||
- [LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum
|
||||
|
||||
## Where can Reticulum be used?
|
||||
Over practically any medium that can support at least a half-duplex channel
|
||||
with 500 bits per second throughput, and an MTU of 500 bytes. Data radios,
|
||||
with greater throughput than 5 bits per second, and an MTU of 500 bytes. Data radios,
|
||||
modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes,
|
||||
WiFi and Ethernet devices, free-space optical links, and similar systems are
|
||||
all examples of the types of physical devices Reticulum can use.
|
||||
@@ -107,14 +110,28 @@ you want to do. For full details and examples, have a look at the
|
||||
[Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html)
|
||||
section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
To simply install Reticulum and related utilities on your system, the easiest way is via pip:
|
||||
To simply install Reticulum and related utilities on your system, the easiest way is via `pip`.
|
||||
You can then start any program that uses Reticulum, or start Reticulum as a system service with
|
||||
[the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
|
||||
|
||||
```bash
|
||||
pip install rns
|
||||
```
|
||||
|
||||
You can then start any program that uses Reticulum, or start Reticulum as a
|
||||
system service with [the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
|
||||
If you are using an operating system that blocks normal user package installation via `pip`,
|
||||
you can return `pip` to normal behaviour by editing the `~/.config/pip/pip.conf` file,
|
||||
and adding the following directive in the `[global]` section:
|
||||
|
||||
```text
|
||||
[global]
|
||||
break-system-packages = true
|
||||
```
|
||||
|
||||
Alternatively, you can use the `pipx` tool to install Reticulum in an isolated environment:
|
||||
|
||||
```bash
|
||||
pipx install rns
|
||||
```
|
||||
|
||||
When first started, Reticulum will create a default configuration file,
|
||||
providing basic connectivity to other Reticulum peers that might be locally
|
||||
@@ -138,12 +155,15 @@ section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/)
|
||||
- An interface status utility called `rnstatus`, that displays information about interfaces
|
||||
- The path lookup and management tool `rnpath` letting you view and modify path tables
|
||||
- A diagnostics tool called `rnprobe` for checking connectivity to destinations
|
||||
- A simple file transfer program called `rncp` making it easy to copy files to remote systems
|
||||
- A simple file transfer program called `rncp` making it easy to transfer files between systems
|
||||
- The identity management and encryption utility `rnid` let's you manage Identities and encrypt/decrypt files
|
||||
- The remote command execution program `rnx` let's you run commands and
|
||||
programs and retrieve output from remote systems
|
||||
|
||||
All tools, including `rnx` and `rncp`, work reliably and well even over very
|
||||
low-bandwidth links like LoRa or Packet Radio.
|
||||
low-bandwidth links like LoRa or Packet Radio. For full-featured remote shells
|
||||
over Reticulum, also have a look at the [rnsh](https://github.com/acehoss/rnsh)
|
||||
program.
|
||||
|
||||
## Supported interface types and devices
|
||||
|
||||
@@ -156,7 +176,7 @@ useful.
|
||||
Currently, the following interfaces are supported:
|
||||
|
||||
- Any Ethernet device
|
||||
- LoRa using [RNode](https://unsigned.io/projects/rnode/)
|
||||
- LoRa using [RNode](https://unsigned.io/rnode/)
|
||||
- Packet Radio TNCs (with or without AX.25)
|
||||
- KISS-compatible hardware and software modems
|
||||
- Any device with a serial port
|
||||
@@ -171,7 +191,7 @@ functionality and performance on low-bandwidth mediums. The goal is to
|
||||
provide a dynamic performance envelope from 250 bits per second, to 1 gigabit
|
||||
per second on normal hardware.
|
||||
|
||||
Currently, the usable performance envelope is approximately 500 bits per second
|
||||
Currently, the usable performance envelope is approximately 150 bits per second
|
||||
to 20 megabits per second, with physical mediums faster than that not being
|
||||
saturated. Performance beyond the current level is intended for future
|
||||
upgrades, but not highly prioritised at this point in time.
|
||||
@@ -189,7 +209,6 @@ these dependencies, and when the `rns` package is installed with `pip`, they
|
||||
will be downloaded and installed as well.
|
||||
|
||||
- [PyCA/cryptography](https://github.com/pyca/cryptography)
|
||||
- [netifaces](https://github.com/al45tair/netifaces)
|
||||
- [pyserial](https://github.com/pyserial/pyserial)
|
||||
|
||||
On more unusual systems, and in some rare cases, it might not be possible to
|
||||
@@ -227,31 +246,25 @@ I2P. Just add one of the following interfaces to your Reticulum configuration
|
||||
file:
|
||||
|
||||
```
|
||||
# TCP/IP interface to the Dublin Hub
|
||||
[[RNS Testnet Dublin]]
|
||||
# TCP/IP interface to the RNS Amsterdam Hub
|
||||
[[RNS Testnet Amsterdam]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = dublin.connect.reticulum.network
|
||||
target_host = amsterdam.connect.reticulum.network
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt Hub
|
||||
[[RNS Testnet Frankfurt]]
|
||||
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
|
||||
[[RNS Testnet BetweenTheBorders]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
target_port = 5377
|
||||
target_host = betweentheborders.com
|
||||
target_port = 4242
|
||||
|
||||
# Interface to I2P Hub A
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
# Interface to Testnet I2P Hub
|
||||
[[RNS Testnet I2P Hub]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = pmlm3l5rpympihoy2o5ago43kluei2jjjzsalcuiuylbve3mwi2a.b32.i2p
|
||||
|
||||
# Interface to I2P Hub B
|
||||
[[RNS Testnet I2P Hub B]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = iwoqtz22dsr73aemwpw7guocplsjjoamyl7sogj33qtcd6ds4mza.b32.i2p
|
||||
peers = g3br23bvx3lq5uddcsjii74xgmn6y5q325ovrkq2zw2wbzbqgbuq.b32.i2p
|
||||
```
|
||||
|
||||
The testnet also contains a number of [Nomad Network](https://github.com/markqvist/nomadnet) nodes, and LXMF propagation nodes.
|
||||
@@ -340,8 +353,8 @@ projects:
|
||||
- [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
|
||||
- [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
|
||||
- [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
|
||||
- [Netifaces](https://github.com/al45tair/netifaces) by [Alastair Houghton](https://github.com/al45tair), *MIT License*
|
||||
- [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
|
||||
- [Six](https://github.com/benjaminp/six) by [Benjamin Peterson](https://github.com/benjaminp), *MIT License*
|
||||
- [ifaddr](https://github.com/pydron/ifaddr) by [Pydron](https://github.com/pydron), *MIT License*
|
||||
- [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
|
||||
- [Python](https://www.python.org)
|
||||
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
import bz2
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from threading import RLock
|
||||
import struct
|
||||
from RNS.Channel import Channel, MessageBase, SystemMessageTypes
|
||||
import RNS
|
||||
from io import RawIOBase, BufferedRWPair, BufferedReader, BufferedWriter
|
||||
from typing import Callable
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
class StreamDataMessage(MessageBase):
|
||||
MSGTYPE = SystemMessageTypes.SMT_STREAM_DATA
|
||||
"""
|
||||
Message type for ``Channel``. ``StreamDataMessage``
|
||||
uses a system-reserved message type.
|
||||
"""
|
||||
|
||||
STREAM_ID_MAX = 0x3fff # 16383
|
||||
"""
|
||||
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
|
||||
"""
|
||||
When the Buffer package is imported, this value is
|
||||
calculcated based on the value of OVERHEAD
|
||||
"""
|
||||
|
||||
def __init__(self, stream_id: int = None, data: bytes = None, eof: bool = False, compressed: bool = False):
|
||||
"""
|
||||
This class is used to encapsulate binary stream
|
||||
data to be sent over a ``Channel``.
|
||||
|
||||
:param stream_id: id of stream relative to receiver
|
||||
:param data: binary data
|
||||
:param eof: set to True if signalling End of File
|
||||
"""
|
||||
super().__init__()
|
||||
if stream_id is not None and stream_id > self.STREAM_ID_MAX:
|
||||
raise ValueError("stream_id must be 0-16383")
|
||||
self.stream_id = stream_id
|
||||
self.compressed = compressed
|
||||
self.data = data or bytes()
|
||||
self.eof = eof
|
||||
|
||||
def pack(self) -> bytes:
|
||||
if self.stream_id is None:
|
||||
raise ValueError("stream_id")
|
||||
|
||||
header_val = (0x3fff & self.stream_id) | (0x8000 if self.eof else 0x0000) | (0x4000 if self.compressed > 0 else 0x0000)
|
||||
return bytes(struct.pack(">H", header_val) + (self.data if self.data else bytes()))
|
||||
|
||||
def unpack(self, raw):
|
||||
self.stream_id = struct.unpack(">H", raw[:2])[0]
|
||||
self.eof = (0x8000 & self.stream_id) > 0
|
||||
self.compressed = (0x4000 & self.stream_id) > 0
|
||||
self.stream_id = self.stream_id & 0x3fff
|
||||
self.data = raw[2:]
|
||||
|
||||
if self.compressed:
|
||||
self.data = bz2.decompress(self.data)
|
||||
|
||||
|
||||
class RawChannelReader(RawIOBase, AbstractContextManager):
|
||||
"""
|
||||
An implementation of RawIOBase that receives
|
||||
binary stream data sent over a ``Channel``.
|
||||
|
||||
This class generally need not be instantiated directly.
|
||||
Use :func:`RNS.Buffer.create_reader`,
|
||||
:func:`RNS.Buffer.create_writer`, and
|
||||
:func:`RNS.Buffer.create_bidirectional_buffer` functions
|
||||
to create buffered streams with optional callbacks.
|
||||
|
||||
For additional information on the API of this
|
||||
object, see the Python documentation for
|
||||
``RawIOBase``.
|
||||
"""
|
||||
def __init__(self, stream_id: int, channel: Channel):
|
||||
"""
|
||||
Create a raw channel reader.
|
||||
|
||||
:param stream_id: local stream id to receive at
|
||||
:param channel: ``Channel`` object to receive from
|
||||
"""
|
||||
self._stream_id = stream_id
|
||||
self._channel = channel
|
||||
self._lock = RLock()
|
||||
self._buffer = bytearray()
|
||||
self._eof = False
|
||||
self._channel._register_message_type(StreamDataMessage, is_system_type=True)
|
||||
self._channel.add_message_handler(self._handle_message)
|
||||
self._listeners: [Callable[[int], None]] = []
|
||||
|
||||
def add_ready_callback(self, cb: Callable[[int], None]):
|
||||
"""
|
||||
Add a function to be called when new data is available.
|
||||
The function should have the signature ``(ready_bytes: int) -> None``
|
||||
|
||||
:param cb: function to call
|
||||
"""
|
||||
with self._lock:
|
||||
self._listeners.append(cb)
|
||||
|
||||
def remove_ready_callback(self, cb: Callable[[int], None]):
|
||||
"""
|
||||
Remove a function added with :func:`RNS.RawChannelReader.add_ready_callback()`
|
||||
|
||||
:param cb: function to remove
|
||||
"""
|
||||
with self._lock:
|
||||
self._listeners.remove(cb)
|
||||
|
||||
def _handle_message(self, message: MessageBase):
|
||||
if isinstance(message, StreamDataMessage):
|
||||
if message.stream_id == self._stream_id:
|
||||
with self._lock:
|
||||
if message.data is not None:
|
||||
self._buffer.extend(message.data)
|
||||
if message.eof:
|
||||
self._eof = True
|
||||
for listener in self._listeners:
|
||||
try:
|
||||
threading.Thread(target=listener, name="Message Callback", args=[len(self._buffer)], daemon=True).start()
|
||||
except Exception as ex:
|
||||
RNS.log("Error calling RawChannelReader(" + str(self._stream_id) + ") callback: " + str(ex), RNS.LOG_ERROR)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _read(self, __size: int) -> bytes | None:
|
||||
with self._lock:
|
||||
result = self._buffer[:__size]
|
||||
self._buffer = self._buffer[__size:]
|
||||
return result if len(result) > 0 or self._eof else None
|
||||
|
||||
def readinto(self, __buffer: bytearray) -> int | None:
|
||||
ready = self._read(len(__buffer))
|
||||
if ready is not None:
|
||||
__buffer[:len(ready)] = ready
|
||||
return len(ready) if ready is not None else None
|
||||
|
||||
def writable(self) -> bool:
|
||||
return False
|
||||
|
||||
def seekable(self) -> bool:
|
||||
return False
|
||||
|
||||
def readable(self) -> bool:
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
with self._lock:
|
||||
self._channel.remove_message_handler(self._handle_message)
|
||||
self._listeners.clear()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
|
||||
class RawChannelWriter(RawIOBase, AbstractContextManager):
|
||||
"""
|
||||
An implementation of RawIOBase that receives
|
||||
binary stream data sent over a channel.
|
||||
|
||||
This class generally need not be instantiated directly.
|
||||
Use :func:`RNS.Buffer.create_reader`,
|
||||
:func:`RNS.Buffer.create_writer`, and
|
||||
:func:`RNS.Buffer.create_bidirectional_buffer` functions
|
||||
to create buffered streams with optional callbacks.
|
||||
|
||||
For additional information on the API of this
|
||||
object, see the Python documentation for
|
||||
``RawIOBase``.
|
||||
"""
|
||||
|
||||
MAX_CHUNK_LEN = 1024*16
|
||||
COMPRESSION_TRIES = 4
|
||||
|
||||
def __init__(self, stream_id: int, channel: Channel):
|
||||
"""
|
||||
Create a raw channel writer.
|
||||
|
||||
:param stream_id: remote stream id to sent do
|
||||
:param channel: ``Channel`` object to send on
|
||||
"""
|
||||
self._stream_id = stream_id
|
||||
self._channel = channel
|
||||
self._eof = False
|
||||
|
||||
def write(self, __b: bytes) -> int | None:
|
||||
try:
|
||||
comp_tries = RawChannelWriter.COMPRESSION_TRIES
|
||||
comp_try = 1
|
||||
comp_success = False
|
||||
chunk_len = len(__b)
|
||||
if chunk_len > RawChannelWriter.MAX_CHUNK_LEN:
|
||||
chunk_len = RawChannelWriter.MAX_CHUNK_LEN
|
||||
__b = __b[:RawChannelWriter.MAX_CHUNK_LEN]
|
||||
chunk_segment = None
|
||||
while chunk_len > 32 and comp_try < comp_tries:
|
||||
chunk_segment_length = int(chunk_len/comp_try)
|
||||
compressed_chunk = bz2.compress(__b[:chunk_segment_length])
|
||||
compressed_length = len(compressed_chunk)
|
||||
if compressed_length < StreamDataMessage.MAX_DATA_LEN and compressed_length < chunk_segment_length:
|
||||
comp_success = True
|
||||
break
|
||||
else:
|
||||
comp_try += 1
|
||||
|
||||
if comp_success:
|
||||
chunk = compressed_chunk
|
||||
processed_length = chunk_segment_length
|
||||
else:
|
||||
chunk = bytes(__b[:StreamDataMessage.MAX_DATA_LEN])
|
||||
processed_length = len(chunk)
|
||||
|
||||
message = StreamDataMessage(self._stream_id, chunk, self._eof, comp_success)
|
||||
|
||||
self._channel.send(message)
|
||||
return processed_length
|
||||
|
||||
except RNS.Channel.ChannelException as cex:
|
||||
if cex.type != RNS.Channel.CEType.ME_LINK_NOT_READY:
|
||||
raise
|
||||
return 0
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
link_rtt = self._channel._outlet.link.rtt
|
||||
timeout = time.time() + (link_rtt * len(self._channel._tx_ring) * 1)
|
||||
except Exception as e:
|
||||
timeout = time.time() + 15
|
||||
|
||||
while time.time() < timeout and not self._channel.is_ready_to_send():
|
||||
time.sleep(0.05)
|
||||
|
||||
self._eof = True
|
||||
self.write(bytes())
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
def seekable(self) -> bool:
|
||||
return False
|
||||
|
||||
def readable(self) -> bool:
|
||||
return False
|
||||
|
||||
def writable(self) -> bool:
|
||||
return True
|
||||
|
||||
class Buffer:
|
||||
"""
|
||||
Static functions for creating buffered streams that send
|
||||
and receive over a ``Channel``.
|
||||
|
||||
These functions use ``BufferedReader``, ``BufferedWriter``,
|
||||
and ``BufferedRWPair`` to add buffering to
|
||||
``RawChannelReader`` and ``RawChannelWriter``.
|
||||
"""
|
||||
@staticmethod
|
||||
def create_reader(stream_id: int, channel: Channel,
|
||||
ready_callback: Callable[[int], None] | None = None) -> BufferedReader:
|
||||
"""
|
||||
Create a buffered reader that reads binary data sent
|
||||
over a ``Channel``, with an optional callback when
|
||||
new data is available.
|
||||
|
||||
Callback signature: ``(ready_bytes: int) -> None``
|
||||
|
||||
For more information on the reader-specific functions
|
||||
of this object, see the Python documentation for
|
||||
``BufferedReader``
|
||||
|
||||
:param stream_id: the local stream id to receive from
|
||||
:param channel: the channel to receive on
|
||||
:param ready_callback: function to call when new data is available
|
||||
:return: a BufferedReader object
|
||||
"""
|
||||
reader = RawChannelReader(stream_id, channel)
|
||||
if ready_callback:
|
||||
reader.add_ready_callback(ready_callback)
|
||||
return BufferedReader(reader)
|
||||
|
||||
@staticmethod
|
||||
def create_writer(stream_id: int, channel: Channel) -> BufferedWriter:
|
||||
"""
|
||||
Create a buffered writer that writes binary data over
|
||||
a ``Channel``.
|
||||
|
||||
For more information on the writer-specific functions
|
||||
of this object, see the Python documentation for
|
||||
``BufferedWriter``
|
||||
|
||||
:param stream_id: the remote stream id to send to
|
||||
:param channel: the channel to send on
|
||||
:return: a BufferedWriter object
|
||||
"""
|
||||
writer = RawChannelWriter(stream_id, channel)
|
||||
return BufferedWriter(writer)
|
||||
|
||||
@staticmethod
|
||||
def create_bidirectional_buffer(receive_stream_id: int, send_stream_id: int, channel: Channel,
|
||||
ready_callback: Callable[[int], None] | None = None) -> BufferedRWPair:
|
||||
"""
|
||||
Create a buffered reader/writer pair that reads and
|
||||
writes binary data over a ``Channel``, with an
|
||||
optional callback when new data is available.
|
||||
|
||||
Callback signature: ``(ready_bytes: int) -> None``
|
||||
|
||||
For more information on the reader-specific functions
|
||||
of this object, see the Python documentation for
|
||||
``BufferedRWPair``
|
||||
|
||||
:param receive_stream_id: the local stream id to receive at
|
||||
:param send_stream_id: the remote stream id to send to
|
||||
:param channel: the channel to send and receive on
|
||||
:param ready_callback: function to call when new data is available
|
||||
:return: a BufferedRWPair object
|
||||
"""
|
||||
reader = RawChannelReader(receive_stream_id, channel)
|
||||
if ready_callback:
|
||||
reader.add_ready_callback(ready_callback)
|
||||
writer = RawChannelWriter(send_stream_id, channel)
|
||||
return BufferedRWPair(reader, writer)
|
||||
+694
@@ -0,0 +1,694 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
import collections
|
||||
import enum
|
||||
import threading
|
||||
import time
|
||||
from types import TracebackType
|
||||
from typing import Type, Callable, TypeVar, Generic, NewType
|
||||
import abc
|
||||
import contextlib
|
||||
import struct
|
||||
import RNS
|
||||
from abc import ABC, abstractmethod
|
||||
TPacket = TypeVar("TPacket")
|
||||
|
||||
class SystemMessageTypes(enum.IntEnum):
|
||||
SMT_STREAM_DATA = 0xff00
|
||||
|
||||
class ChannelOutletBase(ABC, Generic[TPacket]):
|
||||
"""
|
||||
An abstract transport layer interface used by Channel.
|
||||
|
||||
DEPRECATED: This was created for testing; eventually
|
||||
Channel will use Link or a LinkBase interface
|
||||
directly.
|
||||
"""
|
||||
@abstractmethod
|
||||
def send(self, raw: bytes) -> TPacket:
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def resend(self, packet: TPacket) -> TPacket:
|
||||
raise NotImplemented()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def mdu(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def rtt(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_usable(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def get_packet_state(self, packet: TPacket) -> MessageState:
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def timed_out(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def set_packet_timeout_callback(self, packet: TPacket, callback: Callable[[TPacket], None] | None,
|
||||
timeout: float | None = None):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def set_packet_delivered_callback(self, packet: TPacket, callback: Callable[[TPacket], None] | None):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def get_packet_id(self, packet: TPacket) -> any:
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
class CEType(enum.IntEnum):
|
||||
"""
|
||||
ChannelException type codes
|
||||
"""
|
||||
ME_NO_MSG_TYPE = 0
|
||||
ME_INVALID_MSG_TYPE = 1
|
||||
ME_NOT_REGISTERED = 2
|
||||
ME_LINK_NOT_READY = 3
|
||||
ME_ALREADY_SENT = 4
|
||||
ME_TOO_BIG = 5
|
||||
|
||||
|
||||
class ChannelException(Exception):
|
||||
"""
|
||||
An exception thrown by Channel, with a type code.
|
||||
"""
|
||||
def __init__(self, ce_type: CEType, *args):
|
||||
super().__init__(args)
|
||||
self.type = ce_type
|
||||
|
||||
|
||||
class MessageState(enum.IntEnum):
|
||||
"""
|
||||
Set of possible states for a Message
|
||||
"""
|
||||
MSGSTATE_NEW = 0
|
||||
MSGSTATE_SENT = 1
|
||||
MSGSTATE_DELIVERED = 2
|
||||
MSGSTATE_FAILED = 3
|
||||
|
||||
|
||||
class MessageBase(abc.ABC):
|
||||
"""
|
||||
Base type for any messages sent or received on a Channel.
|
||||
Subclasses must define the two abstract methods as well as
|
||||
the ``MSGTYPE`` class variable.
|
||||
"""
|
||||
# MSGTYPE must be unique within all classes sent over a
|
||||
# channel. Additionally, MSGTYPE > 0xf000 are reserved.
|
||||
MSGTYPE = None
|
||||
"""
|
||||
Defines a unique identifier for a message class.
|
||||
|
||||
* Must be unique within all classes registered with a ``Channel``
|
||||
* Must be less than ``0xf000``. Values greater than or equal to ``0xf000`` are reserved.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def pack(self) -> bytes:
|
||||
"""
|
||||
Create and return the binary representation of the message
|
||||
|
||||
:return: binary representation of message
|
||||
"""
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def unpack(self, raw: bytes):
|
||||
"""
|
||||
Populate message from binary representation
|
||||
|
||||
:param raw: binary representation
|
||||
"""
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
MessageCallbackType = NewType("MessageCallbackType", Callable[[MessageBase], bool])
|
||||
|
||||
|
||||
class Envelope:
|
||||
"""
|
||||
Internal wrapper used to transport messages over a channel and
|
||||
track its state within the channel framework.
|
||||
"""
|
||||
def unpack(self, message_factories: dict[int, Type]) -> MessageBase:
|
||||
msgtype, self.sequence, length = struct.unpack(">HHH", self.raw[:6])
|
||||
raw = self.raw[6:]
|
||||
ctor = message_factories.get(msgtype, None)
|
||||
if ctor is None:
|
||||
raise ChannelException(CEType.ME_NOT_REGISTERED, f"Unable to find constructor for Channel MSGTYPE {hex(msgtype)}")
|
||||
message = ctor()
|
||||
message.unpack(raw)
|
||||
self.unpacked = True
|
||||
self.message = message
|
||||
|
||||
return message
|
||||
|
||||
def pack(self) -> bytes:
|
||||
if self.message.__class__.MSGTYPE is None:
|
||||
raise ChannelException(CEType.ME_NO_MSG_TYPE, f"{self.message.__class__} lacks MSGTYPE")
|
||||
data = self.message.pack()
|
||||
self.raw = struct.pack(">HHH", self.message.MSGTYPE, self.sequence, len(data)) + data
|
||||
self.packed = True
|
||||
return self.raw
|
||||
|
||||
def __init__(self, outlet: ChannelOutletBase, message: MessageBase = None, raw: bytes = None, sequence: int = None):
|
||||
self.ts = time.time()
|
||||
self.id = id(self)
|
||||
self.message = message
|
||||
self.raw = raw
|
||||
self.packet: TPacket = None
|
||||
self.sequence = sequence
|
||||
self.outlet = outlet
|
||||
self.tries = 0
|
||||
self.unpacked = False
|
||||
self.packed = False
|
||||
self.tracked = False
|
||||
|
||||
|
||||
class Channel(contextlib.AbstractContextManager):
|
||||
"""
|
||||
Provides reliable delivery of messages over
|
||||
a link.
|
||||
|
||||
``Channel`` differs from ``Request`` and
|
||||
``Resource`` in some important ways:
|
||||
|
||||
**Continuous**
|
||||
Messages can be sent or received as long as
|
||||
the ``Link`` is open.
|
||||
**Bi-directional**
|
||||
Messages can be sent in either direction on
|
||||
the ``Link``; neither end is the client or
|
||||
server.
|
||||
**Size-constrained**
|
||||
Messages must be encoded into a single packet.
|
||||
|
||||
``Channel`` is similar to ``Packet``, except that it
|
||||
provides reliable delivery (automatic retries) as well
|
||||
as a structure for exchanging several types of
|
||||
messages over the ``Link``.
|
||||
|
||||
``Channel`` is not instantiated directly, but rather
|
||||
obtained from a ``Link`` with ``get_channel()``.
|
||||
"""
|
||||
|
||||
# The initial window size at channel setup
|
||||
WINDOW = 2
|
||||
|
||||
# Absolute minimum window size
|
||||
WINDOW_MIN = 2
|
||||
WINDOW_MIN_LIMIT_SLOW = 2
|
||||
WINDOW_MIN_LIMIT_MEDIUM = 5
|
||||
WINDOW_MIN_LIMIT_FAST = 16
|
||||
|
||||
# The maximum window size for transfers on slow links
|
||||
WINDOW_MAX_SLOW = 5
|
||||
|
||||
# The maximum window size for transfers on mid-speed links
|
||||
WINDOW_MAX_MEDIUM = 12
|
||||
|
||||
# The maximum window size for transfers on fast links
|
||||
WINDOW_MAX_FAST = 48
|
||||
|
||||
# For calculating maps and guard segments, this
|
||||
# must be set to the global maximum window.
|
||||
WINDOW_MAX = WINDOW_MAX_FAST
|
||||
|
||||
# If the fast rate is sustained for this many request
|
||||
# rounds, the fast link window size will be allowed.
|
||||
FAST_RATE_THRESHOLD = 10
|
||||
|
||||
# If the RTT rate is higher than this value,
|
||||
# the max window size for fast links will be used.
|
||||
RTT_FAST = 0.18
|
||||
RTT_MEDIUM = 0.75
|
||||
RTT_SLOW = 1.45
|
||||
|
||||
# The minimum allowed flexibility of the window size.
|
||||
# The difference between window_max and window_min
|
||||
# will never be smaller than this value.
|
||||
WINDOW_FLEXIBILITY = 4
|
||||
|
||||
SEQ_MAX = 0xFFFF
|
||||
SEQ_MODULUS = SEQ_MAX+1
|
||||
|
||||
def __init__(self, outlet: ChannelOutletBase):
|
||||
"""
|
||||
|
||||
@param outlet:
|
||||
"""
|
||||
self._outlet = outlet
|
||||
self._lock = threading.RLock()
|
||||
self._tx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._rx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._message_callbacks: [MessageCallbackType] = []
|
||||
self._next_sequence = 0
|
||||
self._next_rx_sequence = 0
|
||||
self._message_factories: dict[int, Type[MessageBase]] = {}
|
||||
self._max_tries = 5
|
||||
self.fast_rate_rounds = 0
|
||||
self.medium_rate_rounds = 0
|
||||
|
||||
if self._outlet.rtt > Channel.RTT_SLOW:
|
||||
self.window = 1
|
||||
self.window_max = 1
|
||||
self.window_min = 1
|
||||
self.window_flexibility = 1
|
||||
else:
|
||||
self.window = Channel.WINDOW
|
||||
self.window_max = Channel.WINDOW_MAX_SLOW
|
||||
self.window_min = Channel.WINDOW_MIN
|
||||
self.window_flexibility = Channel.WINDOW_FLEXIBILITY
|
||||
|
||||
def __enter__(self) -> Channel:
|
||||
return self
|
||||
|
||||
def __exit__(self, __exc_type: Type[BaseException] | None, __exc_value: BaseException | None,
|
||||
__traceback: TracebackType | None) -> bool | None:
|
||||
self._shutdown()
|
||||
return False
|
||||
|
||||
def register_message_type(self, message_class: Type[MessageBase]):
|
||||
"""
|
||||
Register a message class for reception over a ``Channel``.
|
||||
|
||||
Message classes must extend ``MessageBase``.
|
||||
|
||||
:param message_class: Class to register
|
||||
"""
|
||||
self._register_message_type(message_class, is_system_type=False)
|
||||
|
||||
def _register_message_type(self, message_class: Type[MessageBase], *, is_system_type: bool = False):
|
||||
with self._lock:
|
||||
if not issubclass(message_class, MessageBase):
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} is not a subclass of {MessageBase}.")
|
||||
if message_class.MSGTYPE is None:
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} has invalid MSGTYPE class attribute.")
|
||||
if message_class.MSGTYPE >= 0xf000 and not is_system_type:
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} has system-reserved message type.")
|
||||
try:
|
||||
message_class()
|
||||
except Exception as ex:
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} raised an exception when constructed with no arguments: {ex}")
|
||||
|
||||
self._message_factories[message_class.MSGTYPE] = message_class
|
||||
|
||||
def add_message_handler(self, callback: MessageCallbackType):
|
||||
"""
|
||||
Add a handler for incoming messages. A handler
|
||||
has the following signature:
|
||||
|
||||
``(message: MessageBase) -> bool``
|
||||
|
||||
Handlers are processed in the order they are
|
||||
added. If any handler returns True, processing
|
||||
of the message stops; handlers after the
|
||||
returning handler will not be called.
|
||||
|
||||
:param callback: Function to call
|
||||
"""
|
||||
with self._lock:
|
||||
if callback not in self._message_callbacks:
|
||||
self._message_callbacks.append(callback)
|
||||
|
||||
def remove_message_handler(self, callback: MessageCallbackType):
|
||||
"""
|
||||
Remove a handler added with ``add_message_handler``.
|
||||
|
||||
:param callback: handler to remove
|
||||
"""
|
||||
with self._lock:
|
||||
if callback in self._message_callbacks:
|
||||
self._message_callbacks.remove(callback)
|
||||
|
||||
def _shutdown(self):
|
||||
with self._lock:
|
||||
self._message_callbacks.clear()
|
||||
self._clear_rings()
|
||||
|
||||
def _clear_rings(self):
|
||||
with self._lock:
|
||||
for envelope in self._tx_ring:
|
||||
if envelope.packet is not None:
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, None)
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, None)
|
||||
self._tx_ring.clear()
|
||||
self._rx_ring.clear()
|
||||
|
||||
def _emplace_envelope(self, envelope: Envelope, ring: collections.deque[Envelope]) -> bool:
|
||||
with self._lock:
|
||||
i = 0
|
||||
|
||||
for existing in ring:
|
||||
|
||||
if envelope.sequence == existing.sequence:
|
||||
RNS.log(f"Envelope: Emplacement of duplicate envelope with sequence "+str(envelope.sequence), RNS.LOG_EXTREME)
|
||||
return False
|
||||
|
||||
if envelope.sequence < existing.sequence and not (self._next_rx_sequence - envelope.sequence) > (Channel.SEQ_MAX//2):
|
||||
ring.insert(i, envelope)
|
||||
|
||||
envelope.tracked = True
|
||||
return True
|
||||
|
||||
i += 1
|
||||
|
||||
envelope.tracked = True
|
||||
ring.append(envelope)
|
||||
|
||||
return True
|
||||
|
||||
def _run_callbacks(self, message: MessageBase):
|
||||
cbs = self._message_callbacks.copy()
|
||||
|
||||
for cb in cbs:
|
||||
try:
|
||||
if cb(message):
|
||||
return
|
||||
except Exception as e:
|
||||
RNS.log("Channel "+str(self)+" experienced an error while running a message callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def _receive(self, raw: bytes):
|
||||
try:
|
||||
envelope = Envelope(outlet=self._outlet, raw=raw)
|
||||
with self._lock:
|
||||
message = envelope.unpack(self._message_factories)
|
||||
|
||||
if envelope.sequence < self._next_rx_sequence:
|
||||
window_overflow = (self._next_rx_sequence+Channel.WINDOW_MAX) % Channel.SEQ_MODULUS
|
||||
if window_overflow < self._next_rx_sequence:
|
||||
if envelope.sequence > window_overflow:
|
||||
RNS.log("Invalid packet sequence ("+str(envelope.sequence)+") received on channel "+str(self), RNS.LOG_EXTREME)
|
||||
return
|
||||
else:
|
||||
RNS.log("Invalid packet sequence ("+str(envelope.sequence)+") received on channel "+str(self), RNS.LOG_EXTREME)
|
||||
return
|
||||
|
||||
is_new = self._emplace_envelope(envelope, self._rx_ring)
|
||||
|
||||
if not is_new:
|
||||
RNS.log("Duplicate message received on channel "+str(self), RNS.LOG_EXTREME)
|
||||
return
|
||||
else:
|
||||
with self._lock:
|
||||
contigous = []
|
||||
for e in self._rx_ring:
|
||||
if e.sequence == self._next_rx_sequence:
|
||||
contigous.append(e)
|
||||
self._next_rx_sequence = (self._next_rx_sequence + 1) % Channel.SEQ_MODULUS
|
||||
if self._next_rx_sequence == 0:
|
||||
for e in self._rx_ring:
|
||||
if e.sequence == self._next_rx_sequence:
|
||||
contigous.append(e)
|
||||
self._next_rx_sequence = (self._next_rx_sequence + 1) % Channel.SEQ_MODULUS
|
||||
|
||||
for e in contigous:
|
||||
if not e.unpacked:
|
||||
m = e.unpack(self._message_factories)
|
||||
else:
|
||||
m = e.message
|
||||
|
||||
self._rx_ring.remove(e)
|
||||
self._run_callbacks(m)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while receiving data on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def is_ready_to_send(self) -> bool:
|
||||
"""
|
||||
Check if ``Channel`` is ready to send.
|
||||
|
||||
:return: True if ready
|
||||
"""
|
||||
if not self._outlet.is_usable:
|
||||
return False
|
||||
|
||||
with self._lock:
|
||||
outstanding = 0
|
||||
for envelope in self._tx_ring:
|
||||
if envelope.outlet == self._outlet:
|
||||
if not envelope.packet or not self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_DELIVERED:
|
||||
outstanding += 1
|
||||
|
||||
if outstanding >= self.window:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _packet_tx_op(self, packet: TPacket, op: Callable[[TPacket], bool]):
|
||||
with self._lock:
|
||||
envelope = next(filter(lambda e: self._outlet.get_packet_id(e.packet) == self._outlet.get_packet_id(packet),
|
||||
self._tx_ring), None)
|
||||
|
||||
if envelope and op(envelope):
|
||||
envelope.tracked = False
|
||||
if envelope in self._tx_ring:
|
||||
self._tx_ring.remove(envelope)
|
||||
|
||||
if self.window < self.window_max:
|
||||
self.window += 1
|
||||
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Increased "+str(self)+" window to "+str(self.window), RNS.LOG_DEBUG)
|
||||
|
||||
if self._outlet.rtt != 0:
|
||||
if self._outlet.rtt > Channel.RTT_FAST:
|
||||
self.fast_rate_rounds = 0
|
||||
|
||||
if self._outlet.rtt > Channel.RTT_MEDIUM:
|
||||
self.medium_rate_rounds = 0
|
||||
|
||||
else:
|
||||
self.medium_rate_rounds += 1
|
||||
if self.window_max < Channel.WINDOW_MAX_MEDIUM and self.medium_rate_rounds == Channel.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Channel.WINDOW_MAX_MEDIUM
|
||||
self.window_min = Channel.WINDOW_MIN_LIMIT_MEDIUM
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_DEBUG)
|
||||
# RNS.log("Increased "+str(self)+" min window to "+str(self.window_min), RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
self.fast_rate_rounds += 1
|
||||
if self.window_max < Channel.WINDOW_MAX_FAST and self.fast_rate_rounds == Channel.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Channel.WINDOW_MAX_FAST
|
||||
self.window_min = Channel.WINDOW_MIN_LIMIT_FAST
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_DEBUG)
|
||||
# RNS.log("Increased "+str(self)+" min window to "+str(self.window_min), RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
else:
|
||||
RNS.log("Envelope not found in TX ring for "+str(self), RNS.LOG_EXTREME)
|
||||
if not envelope:
|
||||
RNS.log("Spurious message received on "+str(self), RNS.LOG_EXTREME)
|
||||
|
||||
def _packet_delivered(self, packet: TPacket):
|
||||
self._packet_tx_op(packet, lambda env: True)
|
||||
|
||||
def _update_packet_timeouts(self):
|
||||
for envelope in self._tx_ring:
|
||||
updated_timeout = self._get_packet_timeout_time(envelope.tries)
|
||||
if envelope.packet and hasattr(envelope.packet, "receipt") and envelope.packet.receipt and envelope.packet.receipt.timeout:
|
||||
if updated_timeout > envelope.packet.receipt.timeout:
|
||||
envelope.packet.receipt.set_timeout(updated_timeout)
|
||||
|
||||
def _get_packet_timeout_time(self, tries: int) -> float:
|
||||
to = pow(1.5, tries - 1) * max(self._outlet.rtt*2.5, 0.025) * (len(self._tx_ring)+1.5)
|
||||
return to
|
||||
|
||||
def _packet_timeout(self, packet: TPacket):
|
||||
def retry_envelope(envelope: Envelope) -> bool:
|
||||
if envelope.tries >= self._max_tries:
|
||||
RNS.log("Retry count exceeded on "+str(self)+", tearing down Link.", RNS.LOG_ERROR)
|
||||
self._shutdown() # start on separate thread?
|
||||
self._outlet.timed_out()
|
||||
return True
|
||||
|
||||
envelope.tries += 1
|
||||
self._outlet.resend(envelope.packet)
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
self._update_packet_timeouts()
|
||||
|
||||
if self.window > self.window_min:
|
||||
self.window -= 1
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Decreased "+str(self)+" window to "+str(self.window), RNS.LOG_DEBUG)
|
||||
|
||||
if self.window_max > (self.window_min+self.window_flexibility):
|
||||
self.window_max -= 1
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Decreased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_DEBUG)
|
||||
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Decreased "+str(self)+" window to "+str(self.window), RNS.LOG_EXTREME)
|
||||
|
||||
return False
|
||||
|
||||
if self._outlet.get_packet_state(packet) != MessageState.MSGSTATE_DELIVERED:
|
||||
self._packet_tx_op(packet, retry_envelope)
|
||||
|
||||
def send(self, message: MessageBase) -> Envelope:
|
||||
"""
|
||||
Send a message. If a message send is attempted and
|
||||
``Channel`` is not ready, an exception is thrown.
|
||||
|
||||
:param message: an instance of a ``MessageBase`` subclass
|
||||
"""
|
||||
envelope: Envelope | None = None
|
||||
with self._lock:
|
||||
if not self.is_ready_to_send():
|
||||
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
|
||||
|
||||
envelope = Envelope(self._outlet, message=message, sequence=self._next_sequence)
|
||||
self._next_sequence = (self._next_sequence + 1) % Channel.SEQ_MODULUS
|
||||
self._emplace_envelope(envelope, self._tx_ring)
|
||||
|
||||
if envelope is None:
|
||||
raise BlockingIOError()
|
||||
|
||||
envelope.pack()
|
||||
if len(envelope.raw) > self._outlet.mdu:
|
||||
raise ChannelException(CEType.ME_TOO_BIG, f"Packed message too big for packet: {len(envelope.raw)} > {self._outlet.mdu}")
|
||||
|
||||
envelope.packet = self._outlet.send(envelope.raw)
|
||||
envelope.tries += 1
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
self._update_packet_timeouts()
|
||||
|
||||
return envelope
|
||||
|
||||
@property
|
||||
def MDU(self):
|
||||
"""
|
||||
Maximum Data Unit: the number of bytes available
|
||||
for a message to consume in a single send. This
|
||||
value is adjusted from the ``Link`` MDU to accommodate
|
||||
message header information.
|
||||
|
||||
:return: number of bytes available
|
||||
"""
|
||||
return self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
|
||||
|
||||
|
||||
class LinkChannelOutlet(ChannelOutletBase):
|
||||
"""
|
||||
An implementation of ChannelOutletBase for RNS.Link.
|
||||
Allows Channel to send packets over an RNS Link with
|
||||
Packets.
|
||||
|
||||
:param link: RNS Link to wrap
|
||||
"""
|
||||
def __init__(self, link: RNS.Link):
|
||||
self.link = link
|
||||
|
||||
def send(self, raw: bytes) -> RNS.Packet:
|
||||
packet = RNS.Packet(self.link, raw, context=RNS.Packet.CHANNEL)
|
||||
if self.link.status == RNS.Link.ACTIVE:
|
||||
packet.send()
|
||||
return packet
|
||||
|
||||
def resend(self, packet: RNS.Packet) -> RNS.Packet:
|
||||
receipt = packet.resend()
|
||||
if not receipt:
|
||||
RNS.log("Failed to resend packet", RNS.LOG_ERROR)
|
||||
return packet
|
||||
|
||||
@property
|
||||
def mdu(self):
|
||||
return self.link.MDU
|
||||
|
||||
@property
|
||||
def rtt(self):
|
||||
return self.link.rtt
|
||||
|
||||
@property
|
||||
def is_usable(self):
|
||||
return True # had issues looking at Link.status
|
||||
|
||||
def get_packet_state(self, packet: TPacket) -> MessageState:
|
||||
if packet.receipt == None:
|
||||
return MessageState.MSGSTATE_FAILED
|
||||
|
||||
status = packet.receipt.get_status()
|
||||
if status == RNS.PacketReceipt.SENT:
|
||||
return MessageState.MSGSTATE_SENT
|
||||
if status == RNS.PacketReceipt.DELIVERED:
|
||||
return MessageState.MSGSTATE_DELIVERED
|
||||
if status == RNS.PacketReceipt.FAILED:
|
||||
return MessageState.MSGSTATE_FAILED
|
||||
else:
|
||||
raise Exception(f"Unexpected receipt state: {status}")
|
||||
|
||||
def timed_out(self):
|
||||
self.link.teardown()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}({self.link})"
|
||||
|
||||
def set_packet_timeout_callback(self, packet: RNS.Packet, callback: Callable[[RNS.Packet], None] | None,
|
||||
timeout: float | None = None):
|
||||
if timeout and packet.receipt:
|
||||
packet.receipt.set_timeout(timeout)
|
||||
|
||||
def inner(receipt: RNS.PacketReceipt):
|
||||
callback(packet)
|
||||
|
||||
if packet and packet.receipt:
|
||||
packet.receipt.set_timeout_callback(inner if callback else None)
|
||||
|
||||
def set_packet_delivered_callback(self, packet: RNS.Packet, callback: Callable[[RNS.Packet], None] | None):
|
||||
def inner(receipt: RNS.PacketReceipt):
|
||||
callback(packet)
|
||||
|
||||
if packet and packet.receipt:
|
||||
packet.receipt.set_delivery_callback(inner if callback else None)
|
||||
|
||||
def get_packet_id(self, packet: RNS.Packet) -> any:
|
||||
if packet and hasattr(packet, "get_hash") and callable(packet.get_hash):
|
||||
return packet.get_hash()
|
||||
else:
|
||||
return None
|
||||
@@ -45,10 +45,10 @@ class Fernet():
|
||||
|
||||
def __init__(self, key = None):
|
||||
if key == None:
|
||||
raise ValueError("Fernet key cannot be None")
|
||||
raise ValueError("Token key cannot be None")
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("Fernet key must be 32 bytes, not "+str(len(key)))
|
||||
raise ValueError("Token key must be 32 bytes, not "+str(len(key)))
|
||||
|
||||
self._signing_key = key[:16]
|
||||
self._encryption_key = key[16:]
|
||||
@@ -72,7 +72,7 @@ class Fernet():
|
||||
current_time = int(time.time())
|
||||
|
||||
if not isinstance(data, bytes):
|
||||
raise TypeError("Fernet token plaintext input must be bytes")
|
||||
raise TypeError("Token plaintext input must be bytes")
|
||||
|
||||
ciphertext = AES_128_CBC.encrypt(
|
||||
plaintext = PKCS7.pad(data),
|
||||
@@ -87,10 +87,10 @@ class Fernet():
|
||||
|
||||
def decrypt(self, token = None):
|
||||
if not isinstance(token, bytes):
|
||||
raise TypeError("Fernet token must be bytes")
|
||||
raise TypeError("Token must be bytes")
|
||||
|
||||
if not self.verify_hmac(token):
|
||||
raise ValueError("Fernet token HMAC was invalid")
|
||||
raise ValueError("Token HMAC was invalid")
|
||||
|
||||
iv = token[:16]
|
||||
ciphertext = token[16:-32]
|
||||
@@ -107,4 +107,4 @@ class Fernet():
|
||||
return plaintext
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError("Could not decrypt Fernet token")
|
||||
raise ValueError("Could not decrypt token")
|
||||
@@ -39,9 +39,6 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
|
||||
if salt == None or len(salt) == 0:
|
||||
salt = bytes([0] * hash_len)
|
||||
|
||||
if salt == None:
|
||||
salt = b""
|
||||
|
||||
if context == None:
|
||||
context = b""
|
||||
|
||||
|
||||
+11
-3
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -99,7 +99,12 @@ class Destination:
|
||||
name_hash = RNS.Identity.full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
|
||||
addr_hash_material = name_hash
|
||||
if identity != None:
|
||||
addr_hash_material += identity.hash
|
||||
if isinstance(identity, RNS.Identity):
|
||||
addr_hash_material += identity.hash
|
||||
elif isinstance(identity, bytes) and len(identity) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
|
||||
addr_hash_material += identity
|
||||
else:
|
||||
raise TypeError("Invalid material supplied for destination hash calculation")
|
||||
|
||||
return RNS.Identity.full_hash(addr_hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
|
||||
|
||||
@@ -176,6 +181,9 @@ class Destination:
|
||||
"""
|
||||
if self.type != Destination.SINGLE:
|
||||
raise TypeError("Only SINGLE destination types can be announced")
|
||||
|
||||
if self.direction != Destination.IN:
|
||||
raise TypeError("Only IN destination types can be announced")
|
||||
|
||||
now = time.time()
|
||||
stale_responses = []
|
||||
@@ -296,7 +304,7 @@ class Destination:
|
||||
Registers a request handler.
|
||||
|
||||
:param path: The path for the request handler to be registered.
|
||||
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
|
||||
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
|
||||
:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
|
||||
:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
|
||||
:raises: ``ValueError`` if any of the supplied arguments are invalid.
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -217,7 +217,7 @@ class Identity:
|
||||
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
|
||||
|
||||
@staticmethod
|
||||
def validate_announce(packet):
|
||||
def validate_announce(packet, only_validate_signature=False):
|
||||
try:
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
destination_hash = packet.destination_hash
|
||||
@@ -238,6 +238,10 @@ class Identity:
|
||||
announced_identity.load_public_key(public_key)
|
||||
|
||||
if announced_identity.pub != None and announced_identity.validate(signature, signed_data):
|
||||
if only_validate_signature:
|
||||
del announced_identity
|
||||
return True
|
||||
|
||||
hash_material = name_hash+announced_identity.hash
|
||||
expected_hash = RNS.Identity.full_hash(hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
|
||||
|
||||
@@ -452,7 +456,7 @@ class Identity:
|
||||
return False
|
||||
except Exception as e:
|
||||
RNS.log("Error while loading identity from "+str(path), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e))
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def get_salt(self):
|
||||
return self.hash
|
||||
|
||||
@@ -77,8 +77,7 @@ class AX25KISSInterface(Interface):
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -367,5 +366,8 @@ class AX25KISSInterface(Interface):
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "AX25KISSInterface["+self.name+"]"
|
||||
@@ -82,8 +82,7 @@ class KISSInterface(Interface):
|
||||
else:
|
||||
raise SystemError("Android-specific interface was used on non-Android OS")
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -403,5 +402,8 @@ class KISSInterface(Interface):
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
@@ -43,20 +43,22 @@ class KISS():
|
||||
CMD_CR = 0x05
|
||||
CMD_RADIO_STATE = 0x06
|
||||
CMD_RADIO_LOCK = 0x07
|
||||
CMD_ST_ALOCK = 0x0B
|
||||
CMD_LT_ALOCK = 0x0C
|
||||
CMD_DETECT = 0x08
|
||||
CMD_IMPLICIT = 0x09
|
||||
CMD_LEAVE = 0x0A
|
||||
CMD_READY = 0x0F
|
||||
CMD_STAT_RX = 0x21
|
||||
CMD_STAT_TX = 0x22
|
||||
CMD_STAT_RSSI = 0x23
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_STAT_CHTM = 0x25
|
||||
CMD_STAT_PHYPRM = 0x26
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
CMD_FB_READ = 0x42
|
||||
CMD_FB_WRITE = 0x43
|
||||
CMD_FB_READL = 0x44
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
@@ -113,7 +115,7 @@ class AndroidBluetoothManager():
|
||||
if self.bt_enabled():
|
||||
return self.bt_adapter.getDefaultAdapter().getBondedDevices()
|
||||
else:
|
||||
RNS.log("Could not query paired devices, Bluetooth is disabled", RNS.LOG_DEBUG)
|
||||
RNS.log("Could not query paired devices, Bluetooth is disabled", RNS.LOG_EXTREME)
|
||||
return []
|
||||
|
||||
def get_potential_devices(self):
|
||||
@@ -165,8 +167,8 @@ class AndroidBluetoothManager():
|
||||
raise IOError("The Bluetooth RFcomm socket could not be connected: "+str(e))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not create and connect Bluetooth RFcomm socket for "+str(device.getName())+" "+str(device.getAddress()), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Could not create and connect Bluetooth RFcomm socket for "+str(device.getName())+" "+str(device.getAddress()), RNS.LOG_EXTREME)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_EXTREME)
|
||||
|
||||
def close(self):
|
||||
if self.connected:
|
||||
@@ -231,6 +233,10 @@ class RNodeInterface(Interface):
|
||||
RECONNECT_WAIT = 5
|
||||
PORT_IO_TIMEOUT = 3
|
||||
|
||||
Q_SNR_MIN_BASE = -9
|
||||
Q_SNR_MAX = 6
|
||||
Q_SNR_STEP = 2
|
||||
|
||||
@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):
|
||||
@@ -315,7 +321,7 @@ class RNodeInterface(Interface):
|
||||
self, owner, name, port, frequency = None, bandwidth = None, txpower = None,
|
||||
sf = None, cr = None, flow_control = False, id_interval = None,
|
||||
allow_bluetooth = False, target_device_name = None,
|
||||
target_device_address = None, id_callsign = None):
|
||||
target_device_address = None, id_callsign = None, st_alock = None, lt_alock = None):
|
||||
import importlib
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.on_android = True
|
||||
@@ -348,8 +354,7 @@ class RNodeInterface(Interface):
|
||||
else:
|
||||
raise SystemError("Android-specific interface was used on non-Android OS")
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 508
|
||||
|
||||
@@ -373,6 +378,8 @@ class RNodeInterface(Interface):
|
||||
self.cr = cr
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
self.st_alock = st_alock
|
||||
self.lt_alock = lt_alock
|
||||
self.platform = None
|
||||
self.display = None
|
||||
self.mcu = None
|
||||
@@ -395,7 +402,18 @@ class RNodeInterface(Interface):
|
||||
self.r_stat_rx = None
|
||||
self.r_stat_tx = None
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
self.r_st_alock = None
|
||||
self.r_lt_alock = None
|
||||
self.r_random = None
|
||||
self.r_airtime_short = 0.0
|
||||
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.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
@@ -426,6 +444,14 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Invalid coding rate configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.st_alock and (self.st_alock < 0.0 or self.st_alock > 100.0)):
|
||||
RNS.log("Invalid short-term airtime limit configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.lt_alock and (self.lt_alock < 0.0 or self.lt_alock > 100.0)):
|
||||
RNS.log("Invalid long-term airtime limit configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if id_interval != None and id_callsign != None:
|
||||
if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN):
|
||||
self.should_id = True
|
||||
@@ -576,8 +602,11 @@ class RNodeInterface(Interface):
|
||||
raise IOError("Invalid device firmware")
|
||||
|
||||
if self.serial != None and self.port != None:
|
||||
self.timeout = 200
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
|
||||
if self.bt_manager != None and self.bt_manager.connected:
|
||||
self.timeout = 1500
|
||||
RNS.log("Bluetooth connection to RNode now open")
|
||||
|
||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||
@@ -615,6 +644,12 @@ class RNodeInterface(Interface):
|
||||
|
||||
self.setCodingRate()
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setSTALock()
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setLTALock()
|
||||
time.sleep(0.15)
|
||||
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
time.sleep(0.15)
|
||||
@@ -740,6 +775,30 @@ class RNodeInterface(Interface):
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+str(self))
|
||||
|
||||
def setSTALock(self):
|
||||
if self.st_alock != None:
|
||||
at = int(self.st_alock*100)
|
||||
c1 = at >> 8 & 0xFF
|
||||
c2 = at & 0xFF
|
||||
data = KISS.escape(bytes([c1])+bytes([c2]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_ST_ALOCK])+data+bytes([KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring short-term airtime limit for "+str(self))
|
||||
|
||||
def setLTALock(self):
|
||||
if self.lt_alock != None:
|
||||
at = int(self.lt_alock*100)
|
||||
c1 = at >> 8 & 0xFF
|
||||
c2 = at & 0xFF
|
||||
data = KISS.escape(bytes([c1])+bytes([c2]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_LT_ALOCK])+data+bytes([KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring long-term airtime limit for "+str(self))
|
||||
|
||||
def setRadioState(self, state):
|
||||
self.state = state
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||
@@ -809,9 +868,6 @@ class RNodeInterface(Interface):
|
||||
self.owner.inbound(data, self)
|
||||
threading.Thread(target=af, daemon=True).start()
|
||||
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
datalen = len(data)
|
||||
@@ -993,6 +1049,97 @@ class RNodeInterface(Interface):
|
||||
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
||||
elif (command == KISS.CMD_STAT_SNR):
|
||||
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
||||
try:
|
||||
sfs = self.r_sf-7
|
||||
snr = self.r_stat_snr
|
||||
q_snr_min = RNodeInterface.Q_SNR_MIN_BASE-sfs*RNodeInterface.Q_SNR_STEP
|
||||
q_snr_max = RNodeInterface.Q_SNR_MAX
|
||||
q_snr_span = q_snr_max-q_snr_min
|
||||
quality = round(((snr-q_snr_min)/(q_snr_span))*100,1)
|
||||
if quality > 100.0: quality = 100.0
|
||||
if quality < 0.0: quality = 0.0
|
||||
self.r_stat_q = quality
|
||||
except:
|
||||
pass
|
||||
|
||||
elif (command == KISS.CMD_ST_ALOCK):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 2):
|
||||
at = command_buffer[0] << 8 | command_buffer[1]
|
||||
self.r_st_alock = at/100.0
|
||||
RNS.log(str(self)+" Radio reporting short-term airtime limit is "+str(self.r_st_alock)+"%", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_LT_ALOCK):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 2):
|
||||
at = command_buffer[0] << 8 | command_buffer[1]
|
||||
self.r_lt_alock = at/100.0
|
||||
RNS.log(str(self)+" Radio reporting long-term airtime limit is "+str(self.r_lt_alock)+"%", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_STAT_CHTM):
|
||||
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) == 8):
|
||||
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]
|
||||
|
||||
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
|
||||
elif (command == KISS.CMD_STAT_PHYPRM):
|
||||
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) == 10):
|
||||
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]
|
||||
|
||||
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:
|
||||
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)
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_PLATFORM):
|
||||
@@ -1003,7 +1150,7 @@ class RNodeInterface(Interface):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
@@ -1026,7 +1173,7 @@ class RNodeInterface(Interface):
|
||||
if got == 0:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||
RNS.log(str(self)+" serial read timeout", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" serial read timeout in command "+str(command), RNS.LOG_WARNING)
|
||||
data_buffer = b""
|
||||
in_frame = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
@@ -1104,6 +1251,9 @@ class RNodeInterface(Interface):
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
|
||||
@@ -72,8 +72,7 @@ class SerialInterface(Interface):
|
||||
else:
|
||||
raise SystemError("Android-specific interface was used on non-Android OS")
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -254,5 +253,8 @@ class SerialInterface(Interface):
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from collections import deque
|
||||
import socketserver
|
||||
import threading
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
@@ -43,28 +45,39 @@ class AutoInterface(Interface):
|
||||
|
||||
PEERING_TIMEOUT = 7.5
|
||||
|
||||
ALL_IGNORE_IFS = ["lo0"]
|
||||
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
|
||||
ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"]
|
||||
|
||||
BITRATE_GUESS = 10*1000*1000
|
||||
|
||||
MULTI_IF_DEQUE_LEN = 48
|
||||
MULTI_IF_DEQUE_TTL = 0.75
|
||||
|
||||
def handler_factory(self, callback):
|
||||
def create_handler(*args, **keys):
|
||||
return AutoInterfaceHandler(callback, *args, **keys)
|
||||
return create_handler
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
else:
|
||||
RNS.log("Using AutoInterface requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
def descope_linklocal(self, link_local_addr):
|
||||
# Drop scope specifier expressd as %ifname (macOS)
|
||||
link_local_addr = link_local_addr.split("%")[0]
|
||||
# Drop embedded scope specifier (NetBSD, OpenBSD)
|
||||
link_local_addr = re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr)
|
||||
return link_local_addr
|
||||
|
||||
self.netifaces = netifaces
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
def list_interfaces(self):
|
||||
ifs = self.netinfo.interfaces()
|
||||
return ifs
|
||||
|
||||
def list_addresses(self, ifname):
|
||||
ifas = self.netinfo.ifaddresses(ifname)
|
||||
return ifas
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
from RNS.vendor.ifaddr import niwrapper
|
||||
super().__init__()
|
||||
self.netinfo = niwrapper
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
@@ -78,6 +91,8 @@ class AutoInterface(Interface):
|
||||
self.interface_servers = {}
|
||||
self.multicast_echoes = {}
|
||||
self.timed_out_interfaces = {}
|
||||
self.mif_deque = deque(maxlen=AutoInterface.MULTI_IF_DEQUE_LEN)
|
||||
self.mif_deque_times = deque(maxlen=AutoInterface.MULTI_IF_DEQUE_LEN)
|
||||
self.carrier_changed = False
|
||||
|
||||
self.outbound_udp_socket = None
|
||||
@@ -88,6 +103,11 @@ class AutoInterface(Interface):
|
||||
self.peering_timeout = AutoInterface.PEERING_TIMEOUT
|
||||
self.multicast_echo_timeout = AutoInterface.PEERING_TIMEOUT/2
|
||||
|
||||
# Increase peering timeout on Android, due to potential
|
||||
# low-power modes implemented on many chipsets.
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.peering_timeout *= 3
|
||||
|
||||
if allowed_interfaces == None:
|
||||
self.allowed_interfaces = []
|
||||
else:
|
||||
@@ -139,7 +159,7 @@ class AutoInterface(Interface):
|
||||
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
|
||||
|
||||
suitable_interfaces = 0
|
||||
for ifname in self.netifaces.interfaces():
|
||||
for ifname in self.list_interfaces():
|
||||
if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
|
||||
@@ -148,19 +168,21 @@ class AutoInterface(Interface):
|
||||
RNS.log(str(self)+" skipping Android system interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif ifname in self.ignored_interfaces:
|
||||
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif ifname in AutoInterface.ALL_IGNORE_IFS:
|
||||
RNS.log(str(self)+" skipping interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
else:
|
||||
if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME)
|
||||
else:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
addresses = self.list_addresses(ifname)
|
||||
if self.netinfo.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
for address in addresses[self.netinfo.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = address["addr"]
|
||||
self.link_local_addresses.append(link_local_addr.split("%")[0])
|
||||
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
|
||||
link_local_addr = self.descope_linklocal(address["addr"])
|
||||
self.link_local_addresses.append(link_local_addr)
|
||||
self.adopted_interfaces[ifname] = link_local_addr
|
||||
self.multicast_echoes[ifname] = time.time()
|
||||
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
|
||||
@@ -185,7 +207,11 @@ class AutoInterface(Interface):
|
||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
|
||||
|
||||
# Bind socket
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
if self.discovery_scope == AutoInterface.SCOPE_LINK:
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
else:
|
||||
addr_info = socket.getaddrinfo(mcast_addr, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
|
||||
discovery_socket.bind(addr_info[0][4])
|
||||
|
||||
# Set up thread for discovery packets
|
||||
@@ -203,6 +229,11 @@ class AutoInterface(Interface):
|
||||
else:
|
||||
self.receives = True
|
||||
|
||||
if configured_bitrate != None:
|
||||
self.bitrate = configured_bitrate
|
||||
else:
|
||||
self.bitrate = AutoInterface.BITRATE_GUESS
|
||||
|
||||
peering_wait = self.announce_interval*1.2
|
||||
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
|
||||
|
||||
@@ -227,11 +258,6 @@ class AutoInterface(Interface):
|
||||
|
||||
time.sleep(peering_wait)
|
||||
|
||||
if configured_bitrate != None:
|
||||
self.bitrate = configured_bitrate
|
||||
else:
|
||||
self.bitrate = AutoInterface.BITRATE_GUESS
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
@@ -272,13 +298,13 @@ class AutoInterface(Interface):
|
||||
for ifname in self.adopted_interfaces:
|
||||
# Check that the link-local address has not changed
|
||||
try:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
addresses = self.list_addresses(ifname)
|
||||
if self.netinfo.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
for address in addresses[self.netinfo.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = address["addr"].split("%")[0]
|
||||
link_local_addr = self.descope_linklocal(address["addr"])
|
||||
if link_local_addr != self.adopted_interfaces[ifname]:
|
||||
old_link_local_address = self.adopted_interfaces[ifname]
|
||||
RNS.log("Replacing link-local address "+str(old_link_local_address)+" for "+str(ifname)+" with "+str(link_local_addr), RNS.LOG_DEBUG)
|
||||
@@ -374,8 +400,19 @@ class AutoInterface(Interface):
|
||||
self.peers[addr][1] = time.time()
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
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)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
for peer in self.peers:
|
||||
@@ -394,6 +431,11 @@ class AutoInterface(Interface):
|
||||
self.txb += len(data)
|
||||
|
||||
|
||||
# Until per-device sub-interfacing is implemented,
|
||||
# ingress limiting should be disabled on AutoInterface
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "AutoInterface["+self.name+"]"
|
||||
|
||||
|
||||
@@ -390,8 +390,7 @@ class I2PInterfacePeer(Interface):
|
||||
TUNNEL_STATE_STALE = 0x02
|
||||
|
||||
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
@@ -832,8 +831,7 @@ class I2PInterface(Interface):
|
||||
BITRATE_GUESS = 256*1000
|
||||
|
||||
def __init__(self, owner, name, rns_storagepath, peers, connectable = False, ifac_size = 16, ifac_netname = None, ifac_netkey = None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
@@ -964,6 +962,12 @@ class I2PInterface(Interface):
|
||||
def processOutgoing(self, data):
|
||||
pass
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
if from_spawned: self.ia_freq_deque.append(time.time())
|
||||
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def detach(self):
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.i2p.stop()
|
||||
|
||||
+147
-3
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -23,6 +23,7 @@
|
||||
import RNS
|
||||
import time
|
||||
import threading
|
||||
from collections import deque
|
||||
|
||||
class Interface:
|
||||
IN = False
|
||||
@@ -39,18 +40,160 @@ class Interface:
|
||||
MODE_BOUNDARY = 0x05
|
||||
MODE_GATEWAY = 0x06
|
||||
|
||||
# Which interface modes a Transport Node
|
||||
# should actively discover paths for.
|
||||
# Which interface modes a Transport Node should
|
||||
# actively discover paths for.
|
||||
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY]
|
||||
|
||||
# How many samples to use for announce
|
||||
# frequency calculations
|
||||
IA_FREQ_SAMPLES = 6
|
||||
OA_FREQ_SAMPLES = 6
|
||||
|
||||
# Maximum amount of ingress limited announces
|
||||
# to hold at any given time.
|
||||
MAX_HELD_ANNOUNCES = 256
|
||||
|
||||
# How long a spawned interface will be
|
||||
# considered to be newly created. Two
|
||||
# hours by default.
|
||||
IC_NEW_TIME = 2*60*60
|
||||
IC_BURST_FREQ_NEW = 3.5
|
||||
IC_BURST_FREQ = 12
|
||||
IC_BURST_HOLD = 1*60
|
||||
IC_BURST_PENALTY = 5*60
|
||||
IC_HELD_RELEASE_INTERVAL = 30
|
||||
|
||||
def __init__(self):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.created = time.time()
|
||||
self.online = False
|
||||
|
||||
self.ingress_control = True
|
||||
self.ic_max_held_announces = Interface.MAX_HELD_ANNOUNCES
|
||||
self.ic_burst_hold = Interface.IC_BURST_HOLD
|
||||
self.ic_burst_active = False
|
||||
self.ic_burst_activated = 0
|
||||
self.ic_held_release = 0
|
||||
self.ic_burst_freq_new = Interface.IC_BURST_FREQ_NEW
|
||||
self.ic_burst_freq = Interface.IC_BURST_FREQ
|
||||
self.ic_new_time = Interface.IC_NEW_TIME
|
||||
self.ic_burst_penalty = Interface.IC_BURST_PENALTY
|
||||
self.ic_held_release_interval = Interface.IC_HELD_RELEASE_INTERVAL
|
||||
self.held_announces = {}
|
||||
|
||||
self.ia_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
|
||||
self.oa_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
|
||||
|
||||
def get_hash(self):
|
||||
return RNS.Identity.full_hash(str(self).encode("utf-8"))
|
||||
|
||||
# This is a generic function for determining when an interface
|
||||
# should activate ingress limiting. Since this can vary for
|
||||
# different interface types, this function should be overwritten
|
||||
# in case a particular interface requires a different approach.
|
||||
def should_ingress_limit(self):
|
||||
if self.ingress_control:
|
||||
freq_threshold = self.ic_burst_freq_new if self.age() < self.ic_new_time else self.ic_burst_freq
|
||||
ia_freq = self.incoming_announce_frequency()
|
||||
|
||||
if self.ic_burst_active:
|
||||
if ia_freq < freq_threshold and time.time() > self.ic_burst_activated+self.ic_burst_hold:
|
||||
self.ic_burst_active = False
|
||||
self.ic_held_release = time.time() + self.ic_burst_penalty
|
||||
return True
|
||||
|
||||
else:
|
||||
if ia_freq > freq_threshold:
|
||||
self.ic_burst_active = True
|
||||
self.ic_burst_activated = time.time()
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def age(self):
|
||||
return time.time()-self.created
|
||||
|
||||
def hold_announce(self, announce_packet):
|
||||
if announce_packet.destination_hash in self.held_announces:
|
||||
self.held_announces[announce_packet.destination_hash] = announce_packet
|
||||
elif not len(self.held_announces) >= self.ic_max_held_announces:
|
||||
self.held_announces[announce_packet.destination_hash] = announce_packet
|
||||
|
||||
def process_held_announces(self):
|
||||
try:
|
||||
if not self.should_ingress_limit() and len(self.held_announces) > 0 and time.time() > self.ic_held_release:
|
||||
freq_threshold = self.ic_burst_freq_new if self.age() < self.ic_new_time else self.ic_burst_freq
|
||||
ia_freq = self.incoming_announce_frequency()
|
||||
if ia_freq < freq_threshold:
|
||||
selected_announce_packet = None
|
||||
min_hops = RNS.Transport.PATHFINDER_M
|
||||
for destination_hash in self.held_announces:
|
||||
announce_packet = self.held_announces[destination_hash]
|
||||
if announce_packet.hops < min_hops:
|
||||
min_hops = announce_packet.hops
|
||||
selected_announce_packet = announce_packet
|
||||
|
||||
if selected_announce_packet != None:
|
||||
RNS.log("Releasing held announce packet "+str(selected_announce_packet)+" from "+str(self), RNS.LOG_EXTREME)
|
||||
self.ic_held_release = time.time() + self.ic_held_release_interval
|
||||
self.held_announces.pop(selected_announce_packet.destination_hash)
|
||||
def release():
|
||||
RNS.Transport.inbound(selected_announce_packet.raw, selected_announce_packet.receiving_interface)
|
||||
threading.Thread(target=release, daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while processing held announces for "+str(self), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def received_announce(self):
|
||||
self.ia_freq_deque.append(time.time())
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.received_announce(from_spawned=True)
|
||||
|
||||
def sent_announce(self):
|
||||
self.oa_freq_deque.append(time.time())
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.sent_announce(from_spawned=True)
|
||||
|
||||
def incoming_announce_frequency(self):
|
||||
if not len(self.ia_freq_deque) > 1:
|
||||
return 0
|
||||
else:
|
||||
dq_len = len(self.ia_freq_deque)
|
||||
delta_sum = 0
|
||||
for i in range(1,dq_len):
|
||||
delta_sum += self.ia_freq_deque[i]-self.ia_freq_deque[i-1]
|
||||
delta_sum += time.time() - self.ia_freq_deque[dq_len-1]
|
||||
|
||||
if delta_sum == 0:
|
||||
avg = 0
|
||||
else:
|
||||
avg = 1/(delta_sum/(dq_len))
|
||||
|
||||
return avg
|
||||
|
||||
def outgoing_announce_frequency(self):
|
||||
if not len(self.oa_freq_deque) > 1:
|
||||
return 0
|
||||
else:
|
||||
dq_len = len(self.oa_freq_deque)
|
||||
delta_sum = 0
|
||||
for i in range(1,dq_len):
|
||||
delta_sum += self.oa_freq_deque[i]-self.oa_freq_deque[i-1]
|
||||
delta_sum += time.time() - self.oa_freq_deque[dq_len-1]
|
||||
|
||||
if delta_sum == 0:
|
||||
avg = 0
|
||||
else:
|
||||
avg = 1/(delta_sum/(dq_len))
|
||||
|
||||
return avg
|
||||
|
||||
def process_announce_queue(self):
|
||||
if not hasattr(self, "announce_cap"):
|
||||
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
|
||||
@@ -79,6 +222,7 @@ class Interface:
|
||||
self.announce_allowed_at = now + wait_time
|
||||
|
||||
self.processOutgoing(selected["raw"])
|
||||
self.sent_announce()
|
||||
|
||||
if selected in self.announce_queue:
|
||||
self.announce_queue.remove(selected)
|
||||
|
||||
@@ -70,8 +70,7 @@ class KISSInterface(Interface):
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -349,5 +348,8 @@ class KISSInterface(Interface):
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -28,6 +28,7 @@ import time
|
||||
import sys
|
||||
import os
|
||||
import RNS
|
||||
from threading import Lock
|
||||
|
||||
class HDLC():
|
||||
FLAG = 0x7E
|
||||
@@ -41,14 +42,19 @@ class HDLC():
|
||||
return data
|
||||
|
||||
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
def server_bind(self):
|
||||
if RNS.vendor.platformutils.is_windows():
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
|
||||
else:
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind(self.server_address)
|
||||
self.server_address = self.socket.getsockname()
|
||||
|
||||
class LocalClientInterface(Interface):
|
||||
RECONNECT_WAIT = 3
|
||||
RECONNECT_WAIT = 8
|
||||
|
||||
def __init__(self, owner, name, target_port = None, connected_socket=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
# TODO: Remove at some point
|
||||
# self.rxptime = 0
|
||||
@@ -72,6 +78,7 @@ class LocalClientInterface(Interface):
|
||||
self.target_ip = None
|
||||
self.target_port = None
|
||||
self.socket = connected_socket
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
self.is_connected_to_shared_instance = False
|
||||
|
||||
@@ -86,6 +93,8 @@ class LocalClientInterface(Interface):
|
||||
self.online = True
|
||||
self.writing = False
|
||||
|
||||
self._force_bitrate = False
|
||||
|
||||
self.announce_rate_target = None
|
||||
self.announce_rate_grace = None
|
||||
self.announce_rate_penalty = None
|
||||
@@ -95,8 +104,12 @@ class LocalClientInterface(Interface):
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def connect(self):
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
|
||||
self.online = True
|
||||
@@ -129,7 +142,10 @@ class LocalClientInterface(Interface):
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
RNS.Transport.shared_connection_reappeared()
|
||||
def job():
|
||||
time.sleep(LocalClientInterface.RECONNECT_WAIT+2)
|
||||
RNS.Transport.shared_connection_reappeared()
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
else:
|
||||
RNS.log("Attempt to reconnect on a non-initiator shared local interface. This should not happen.", RNS.LOG_ERROR)
|
||||
@@ -154,6 +170,16 @@ class LocalClientInterface(Interface):
|
||||
if self.online:
|
||||
try:
|
||||
self.writing = True
|
||||
|
||||
if self._force_bitrate:
|
||||
if not hasattr(self, "send_lock"):
|
||||
self.send_lock = Lock()
|
||||
|
||||
with self.send_lock:
|
||||
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])
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
@@ -246,7 +272,8 @@ class LocalClientInterface(Interface):
|
||||
RNS.Transport.local_client_interfaces.remove(self)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.clients -= 1
|
||||
RNS.Transport.owner._should_persist_data()
|
||||
if hasattr(RNS.Transport, "owner") and RNS.Transport.owner != None:
|
||||
RNS.Transport.owner._should_persist_data()
|
||||
|
||||
if nowarning == False:
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
@@ -267,8 +294,7 @@ class LocalClientInterface(Interface):
|
||||
class LocalServerInterface(Interface):
|
||||
|
||||
def __init__(self, owner, bindport=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
self.online = False
|
||||
self.clients = 0
|
||||
|
||||
@@ -292,7 +318,6 @@ class LocalServerInterface(Interface):
|
||||
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
@@ -317,7 +342,9 @@ class LocalServerInterface(Interface):
|
||||
spawned_interface.target_port = str(handler.client_address[1])
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.bitrate = self.bitrate
|
||||
RNS.log("Accepting new connection to shared instance: "+str(spawned_interface), RNS.LOG_EXTREME)
|
||||
if hasattr(self, "_force_bitrate"):
|
||||
spawned_interface._force_bitrate = self._force_bitrate
|
||||
# RNS.log("Accepting new connection to shared instance: "+str(spawned_interface), RNS.LOG_EXTREME)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.local_client_interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
@@ -326,6 +353,12 @@ class LocalServerInterface(Interface):
|
||||
def processOutgoing(self, data):
|
||||
pass
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
if from_spawned: self.ia_freq_deque.append(time.time())
|
||||
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def __str__(self):
|
||||
return "Shared Instance["+str(self.bind_port)+"]"
|
||||
|
||||
|
||||
@@ -54,8 +54,7 @@ class PipeInterface(Interface):
|
||||
if respawn_delay == None:
|
||||
respawn_delay = 5
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -43,6 +43,8 @@ class KISS():
|
||||
CMD_CR = 0x05
|
||||
CMD_RADIO_STATE = 0x06
|
||||
CMD_RADIO_LOCK = 0x07
|
||||
CMD_ST_ALOCK = 0x0B
|
||||
CMD_LT_ALOCK = 0x0C
|
||||
CMD_DETECT = 0x08
|
||||
CMD_LEAVE = 0x0A
|
||||
CMD_READY = 0x0F
|
||||
@@ -50,11 +52,14 @@ class KISS():
|
||||
CMD_STAT_TX = 0x22
|
||||
CMD_STAT_RSSI = 0x23
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_STAT_CHTM = 0x25
|
||||
CMD_STAT_PHYPRM = 0x26
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
CMD_FB_READ = 0x42
|
||||
CMD_FB_WRITE = 0x43
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
CMD_FW_VERSION = 0x50
|
||||
@@ -98,7 +103,11 @@ class RNodeInterface(Interface):
|
||||
|
||||
RECONNECT_WAIT = 5
|
||||
|
||||
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):
|
||||
Q_SNR_MIN_BASE = -9
|
||||
Q_SNR_MAX = 6
|
||||
Q_SNR_STEP = 2
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None, st_alock = None, lt_alock = None):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
raise SystemError("Invlaid interface type. The Android-specific RNode interface must be used on Android")
|
||||
|
||||
@@ -110,8 +119,7 @@ class RNodeInterface(Interface):
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 508
|
||||
|
||||
@@ -125,6 +133,8 @@ class RNodeInterface(Interface):
|
||||
self.stopbits = 1
|
||||
self.timeout = 100
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.reconnecting= False
|
||||
|
||||
self.frequency = frequency
|
||||
self.bandwidth = bandwidth
|
||||
@@ -133,6 +143,8 @@ class RNodeInterface(Interface):
|
||||
self.cr = cr
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
self.st_alock = st_alock
|
||||
self.lt_alock = lt_alock
|
||||
self.platform = None
|
||||
self.display = None
|
||||
self.mcu = None
|
||||
@@ -155,7 +167,18 @@ class RNodeInterface(Interface):
|
||||
self.r_stat_rx = None
|
||||
self.r_stat_tx = None
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
self.r_st_alock = None
|
||||
self.r_lt_alock = None
|
||||
self.r_random = None
|
||||
self.r_airtime_short = 0.0
|
||||
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.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
@@ -183,6 +206,14 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Invalid coding rate configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.st_alock and (self.st_alock < 0.0 or self.st_alock > 100.0)):
|
||||
RNS.log("Invalid short-term airtime limit configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.lt_alock and (self.lt_alock < 0.0 or self.lt_alock > 100.0)):
|
||||
RNS.log("Invalid long-term airtime limit configured for "+str(self), RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if id_interval != None and id_callsign != None:
|
||||
if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN):
|
||||
self.should_id = True
|
||||
@@ -210,9 +241,10 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
def open_port(self):
|
||||
@@ -233,7 +265,15 @@ class RNodeInterface(Interface):
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
self.r_frequency = None
|
||||
self.r_bandwidth = None
|
||||
self.r_txpower = None
|
||||
self.r_sf = None
|
||||
self.r_cr = None
|
||||
self.r_state = None
|
||||
self.r_lock = None
|
||||
sleep(2.0)
|
||||
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
@@ -242,7 +282,8 @@ class RNodeInterface(Interface):
|
||||
sleep(0.2)
|
||||
|
||||
if not self.detected:
|
||||
raise IOError("Could not detect device")
|
||||
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
self.display = True
|
||||
@@ -260,7 +301,6 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
||||
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
raise IOError("RNode interface did not pass configuration validation")
|
||||
|
||||
|
||||
def initRadio(self):
|
||||
@@ -269,6 +309,8 @@ class RNodeInterface(Interface):
|
||||
self.setTXPower()
|
||||
self.setSpreadingFactor()
|
||||
self.setCodingRate()
|
||||
self.setSTALock()
|
||||
self.setLTALock()
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
|
||||
def detect(self):
|
||||
@@ -373,6 +415,30 @@ class RNodeInterface(Interface):
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+str(self))
|
||||
|
||||
def setSTALock(self):
|
||||
if self.st_alock != None:
|
||||
at = int(self.st_alock*100)
|
||||
c1 = at >> 8 & 0xFF
|
||||
c2 = at & 0xFF
|
||||
data = KISS.escape(bytes([c1])+bytes([c2]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_ST_ALOCK])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring short-term airtime limit for "+str(self))
|
||||
|
||||
def setLTALock(self):
|
||||
if self.lt_alock != None:
|
||||
at = int(self.lt_alock*100)
|
||||
c1 = at >> 8 & 0xFF
|
||||
c2 = at & 0xFF
|
||||
data = KISS.escape(bytes([c1])+bytes([c2]))
|
||||
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_LT_ALOCK])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring long-term airtime limit for "+str(self))
|
||||
|
||||
def setRadioState(self, state):
|
||||
self.state = state
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||
@@ -395,7 +461,7 @@ class RNodeInterface(Interface):
|
||||
|
||||
|
||||
def validateRadioState(self):
|
||||
RNS.log("Wating for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
sleep(0.25);
|
||||
|
||||
self.validcfg = True
|
||||
@@ -610,6 +676,96 @@ class RNodeInterface(Interface):
|
||||
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
||||
elif (command == KISS.CMD_STAT_SNR):
|
||||
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
||||
try:
|
||||
sfs = self.r_sf-7
|
||||
snr = self.r_stat_snr
|
||||
q_snr_min = RNodeInterface.Q_SNR_MIN_BASE-sfs*RNodeInterface.Q_SNR_STEP
|
||||
q_snr_max = RNodeInterface.Q_SNR_MAX
|
||||
q_snr_span = q_snr_max-q_snr_min
|
||||
quality = round(((snr-q_snr_min)/(q_snr_span))*100,1)
|
||||
if quality > 100.0: quality = 100.0
|
||||
if quality < 0.0: quality = 0.0
|
||||
self.r_stat_q = quality
|
||||
except:
|
||||
pass
|
||||
elif (command == KISS.CMD_ST_ALOCK):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 2):
|
||||
at = command_buffer[0] << 8 | command_buffer[1]
|
||||
self.r_st_alock = at/100.0
|
||||
RNS.log(str(self)+" Radio reporting short-term airtime limit is "+str(self.r_st_alock)+"%", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_LT_ALOCK):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 2):
|
||||
at = command_buffer[0] << 8 | command_buffer[1]
|
||||
self.r_lt_alock = at/100.0
|
||||
RNS.log(str(self)+" Radio reporting long-term airtime limit is "+str(self.r_lt_alock)+"%", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_STAT_CHTM):
|
||||
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) == 8):
|
||||
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]
|
||||
|
||||
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
|
||||
elif (command == KISS.CMD_STAT_PHYPRM):
|
||||
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) == 10):
|
||||
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]
|
||||
|
||||
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:
|
||||
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)
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_PLATFORM):
|
||||
@@ -620,7 +776,7 @@ class RNodeInterface(Interface):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
@@ -643,7 +799,7 @@ class RNodeInterface(Interface):
|
||||
else:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||
RNS.log(str(self)+" serial read timeout", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" serial read timeout in command "+str(command), RNS.LOG_WARNING)
|
||||
data_buffer = b""
|
||||
in_frame = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
@@ -668,11 +824,17 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try:
|
||||
self.serial.close()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if not self.detached and not self.reconnecting:
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
@@ -682,13 +844,18 @@ class RNodeInterface(Interface):
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
self.reconnecting = False
|
||||
if self.online:
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.disable_external_framebuffer()
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
@@ -60,8 +60,7 @@ class SerialInterface(Interface):
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -201,5 +200,8 @@ class SerialInterface(Interface):
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
|
||||
@@ -79,8 +79,7 @@ class TCPClientInterface(Interface):
|
||||
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):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
@@ -117,6 +116,8 @@ class TCPClientInterface(Interface):
|
||||
elif platform.system() == "Darwin":
|
||||
self.set_timeouts_osx()
|
||||
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
elif target_ip != None and target_port != None:
|
||||
self.receives = True
|
||||
self.target_ip = target_ip
|
||||
@@ -201,6 +202,7 @@ class TCPClientInterface(Interface):
|
||||
|
||||
self.socket = socket.socket(socket.AF_INET, 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.settimeout(None)
|
||||
self.online = True
|
||||
@@ -408,29 +410,18 @@ class TCPServerInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
@@ -515,6 +506,12 @@ class TCPServerInterface(Interface):
|
||||
self.clients += 1
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
if from_spawned: self.ia_freq_deque.append(time.time())
|
||||
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def processOutgoing(self, data):
|
||||
pass
|
||||
|
||||
|
||||
@@ -34,29 +34,18 @@ class UDPInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
|
||||
+290
-142
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
|
||||
from RNS.Cryptography import Fernet
|
||||
from RNS.Channel import Channel, LinkChannelOutlet
|
||||
|
||||
from time import sleep
|
||||
from .vendor import umsgpack as umsgpack
|
||||
import threading
|
||||
import inspect
|
||||
import math
|
||||
import time
|
||||
import RNS
|
||||
@@ -110,9 +112,10 @@ class Link:
|
||||
link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE])
|
||||
link.set_link_id(packet)
|
||||
link.destination = packet.destination
|
||||
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops)
|
||||
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"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
|
||||
link.prove()
|
||||
@@ -146,10 +149,14 @@ class Link:
|
||||
self.pending_requests = []
|
||||
self.last_inbound = 0
|
||||
self.last_outbound = 0
|
||||
self.last_proof = 0
|
||||
self.tx = 0
|
||||
self.rx = 0
|
||||
self.txbytes = 0
|
||||
self.rxbytes = 0
|
||||
self.rssi = None
|
||||
self.snr = None
|
||||
self.q = None
|
||||
self.traffic_timeout_factor = Link.TRAFFIC_TIMEOUT_FACTOR
|
||||
self.keepalive_timeout_factor = Link.KEEPALIVE_TIMEOUT_FACTOR
|
||||
self.keepalive = Link.KEEPALIVE
|
||||
@@ -162,13 +169,16 @@ class Link:
|
||||
self.destination = destination
|
||||
self.attached_interface = None
|
||||
self.__remote_identity = None
|
||||
self.__track_phy_stats = False
|
||||
self._channel = None
|
||||
if self.destination == None:
|
||||
self.initiator = False
|
||||
self.prv = X25519PrivateKey.generate()
|
||||
self.sig_prv = self.owner.identity.sig_prv
|
||||
else:
|
||||
self.initiator = True
|
||||
self.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash))
|
||||
self.establishment_timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(destination.hash)
|
||||
self.establishment_timeout += Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash))
|
||||
self.prv = X25519PrivateKey.generate()
|
||||
self.sig_prv = Ed25519PrivateKey.generate()
|
||||
|
||||
@@ -204,6 +214,7 @@ class Link:
|
||||
self.packet.send()
|
||||
self.had_outbound()
|
||||
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_DEBUG)
|
||||
RNS.log(f"Establishment timeout is {RNS.prettytime(self.establishment_timeout)} for link request "+RNS.prettyhexrep(self.link_id), RNS.LOG_EXTREME)
|
||||
|
||||
|
||||
def load_peer(self, peer_pub_bytes, peer_sig_pub_bytes):
|
||||
@@ -221,15 +232,18 @@ class Link:
|
||||
self.hash = self.link_id
|
||||
|
||||
def handshake(self):
|
||||
self.status = Link.HANDSHAKE
|
||||
self.shared_key = self.prv.exchange(self.peer_pub)
|
||||
if self.status == Link.PENDING and self.prv != None:
|
||||
self.status = Link.HANDSHAKE
|
||||
self.shared_key = self.prv.exchange(self.peer_pub)
|
||||
|
||||
self.derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=self.shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
self.derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=self.shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
else:
|
||||
RNS.log("Handshake attempt on "+str(self)+" with invalid state "+str(self.status), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def prove(self):
|
||||
@@ -257,36 +271,50 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
def validate_proof(self, packet):
|
||||
if self.status == Link.PENDING:
|
||||
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]
|
||||
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
|
||||
self.handshake()
|
||||
try:
|
||||
if self.status == Link.PENDING:
|
||||
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]
|
||||
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
|
||||
self.handshake()
|
||||
|
||||
self.establishment_cost += len(packet.raw)
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if self.destination.identity.validate(signature, signed_data):
|
||||
self.rtt = time.time() - self.request_time
|
||||
self.attached_interface = packet.receiving_interface
|
||||
self.__remote_identity = self.destination.identity
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
self.establishment_cost += len(packet.raw)
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if self.destination.identity.validate(signature, signed_data):
|
||||
if self.status != Link.HANDSHAKE:
|
||||
raise IOError("Invalid link state for proof validation: "+str(self.status))
|
||||
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
else:
|
||||
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
||||
self.rtt = time.time() - self.request_time
|
||||
self.attached_interface = packet.receiving_interface
|
||||
self.__remote_identity = self.destination.identity
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
self.last_proof = self.activated_at
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
|
||||
|
||||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||
self.establishment_rate = self.establishment_cost/self.rtt
|
||||
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
else:
|
||||
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
self.status = Link.CLOSED
|
||||
RNS.log("An error ocurred while validating link request proof on "+str(self)+".", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def identify(self, identity):
|
||||
@@ -298,7 +326,7 @@ class Link:
|
||||
|
||||
:param identity: An RNS.Identity instance to identify as.
|
||||
"""
|
||||
if self.initiator:
|
||||
if self.initiator and self.status == Link.ACTIVE:
|
||||
signed_data = self.link_id + identity.get_public_key()
|
||||
signature = identity.sign(signed_data)
|
||||
proof_data = identity.get_public_key() + signature
|
||||
@@ -362,25 +390,68 @@ class Link:
|
||||
|
||||
def rtt_packet(self, packet):
|
||||
try:
|
||||
# TODO: This is crude, we should use the delta
|
||||
# to model a more representative per-bit round
|
||||
# trip time, and use that to set a sensible RTT
|
||||
# expectancy for the link. This will have to do
|
||||
# for now though.
|
||||
measured_rtt = time.time() - self.request_time
|
||||
plaintext = self.decrypt(packet.data)
|
||||
rtt = umsgpack.unpackb(plaintext)
|
||||
self.rtt = max(measured_rtt, rtt)
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
if plaintext != None:
|
||||
rtt = umsgpack.unpackb(plaintext)
|
||||
self.rtt = max(measured_rtt, rtt)
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
|
||||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||
self.establishment_rate = self.establishment_cost/self.rtt
|
||||
|
||||
try:
|
||||
if self.owner.callbacks.link_established != None:
|
||||
self.owner.callbacks.link_established(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred in external link establishment callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
if self.owner.callbacks.link_established != None:
|
||||
self.owner.callbacks.link_established(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
|
||||
def track_phy_stats(self, track):
|
||||
"""
|
||||
You can enable physical layer statistics on a per-link basis. If this is enabled,
|
||||
and the link is running over an interface that supports reporting physical layer
|
||||
statistics, you will be able to retrieve stats such as *RSSI*, *SNR* and physical
|
||||
*Link Quality* for the link.
|
||||
|
||||
:param track: Whether or not to keep track of physical layer statistics. Value must be ``True`` or ``False``.
|
||||
"""
|
||||
if track:
|
||||
self.__track_phy_stats = True
|
||||
else:
|
||||
self.__track_phy_stats = False
|
||||
|
||||
def get_rssi(self):
|
||||
"""
|
||||
: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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def get_establishment_rate(self):
|
||||
"""
|
||||
:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
|
||||
"""
|
||||
if self.establishment_rate != None:
|
||||
return self.establishment_rate*8
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_salt(self):
|
||||
return self.link_id
|
||||
|
||||
@@ -441,6 +512,7 @@ class Link:
|
||||
self.teardown_reason = Link.DESTINATION_CLOSED
|
||||
else:
|
||||
self.teardown_reason = Link.INITIATOR_CLOSED
|
||||
self.__update_phy_stats(packet)
|
||||
self.link_closed()
|
||||
except Exception as e:
|
||||
pass
|
||||
@@ -450,6 +522,8 @@ class Link:
|
||||
resource.cancel()
|
||||
for resource in self.outgoing_resources:
|
||||
resource.cancel()
|
||||
if self._channel:
|
||||
self._channel._shutdown()
|
||||
|
||||
self.prv = None
|
||||
self.pub = None
|
||||
@@ -477,7 +551,11 @@ class Link:
|
||||
def __watchdog_job(self):
|
||||
while not self.status == Link.CLOSED:
|
||||
while (self.watchdog_lock):
|
||||
sleep(max(self.rtt, 0.025))
|
||||
rtt_wait = 0.025
|
||||
if hasattr(self, "rtt") and self.rtt:
|
||||
rtt_wait = self.rtt
|
||||
|
||||
sleep(max(rtt_wait, 0.025))
|
||||
|
||||
if not self.status == Link.CLOSED:
|
||||
# Link was initiated, but no response
|
||||
@@ -496,19 +574,19 @@ class Link:
|
||||
next_check = self.request_time + self.establishment_timeout
|
||||
sleep_time = next_check - time.time()
|
||||
if time.time() >= self.request_time + self.establishment_timeout:
|
||||
if self.initiator:
|
||||
RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
|
||||
|
||||
self.status = Link.CLOSED
|
||||
self.teardown_reason = Link.TIMEOUT
|
||||
self.link_closed()
|
||||
sleep_time = 0.001
|
||||
|
||||
if self.initiator:
|
||||
RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
|
||||
|
||||
elif self.status == Link.ACTIVE:
|
||||
activated_at = self.activated_at if self.activated_at != None else 0
|
||||
last_inbound = max(self.last_inbound, activated_at)
|
||||
last_inbound = max(max(self.last_inbound, self.last_proof), activated_at)
|
||||
|
||||
if time.time() >= last_inbound + self.keepalive:
|
||||
if self.initiator:
|
||||
@@ -540,6 +618,21 @@ class Link:
|
||||
sleep(sleep_time)
|
||||
|
||||
|
||||
def __update_phy_stats(self, packet, query_shared = True):
|
||||
if self.__track_phy_stats:
|
||||
if query_shared:
|
||||
reticulum = RNS.Reticulum.get_instance()
|
||||
if packet.rssi == None: packet.rssi = reticulum.get_packet_rssi(packet.packet_hash)
|
||||
if packet.snr == None: packet.snr = reticulum.get_packet_snr(packet.packet_hash)
|
||||
if packet.q == None: packet.q = reticulum.get_packet_q(packet.packet_hash)
|
||||
|
||||
if packet.rssi != None:
|
||||
self.rssi = packet.rssi
|
||||
if packet.snr != None:
|
||||
self.snr = packet.snr
|
||||
if packet.q != None:
|
||||
self.q = packet.q
|
||||
|
||||
def send_keepalive(self):
|
||||
keepalive_packet = RNS.Packet(self, bytes([0xFF]), context=RNS.Packet.KEEPALIVE)
|
||||
keepalive_packet.send()
|
||||
@@ -568,7 +661,13 @@ class Link:
|
||||
|
||||
if allowed:
|
||||
RNS.log("Handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_DEBUG)
|
||||
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
|
||||
if len(inspect.signature(response_generator).parameters) == 5:
|
||||
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
|
||||
elif len(inspect.signature(response_generator).parameters) == 6:
|
||||
response = response_generator(path, request_data, request_id, self.link_id, self.__remote_identity, requested_at)
|
||||
else:
|
||||
raise TypeError("Invalid signature for response generator callback")
|
||||
|
||||
if response != None:
|
||||
packed_response = umsgpack.packb([request_id, response])
|
||||
|
||||
@@ -624,6 +723,16 @@ class Link:
|
||||
if pending_request.request_id == resource.request_id:
|
||||
pending_request.request_timed_out(None)
|
||||
|
||||
def get_channel(self):
|
||||
"""
|
||||
Get the ``Channel`` for this link.
|
||||
|
||||
:return: ``Channel`` object
|
||||
"""
|
||||
if self._channel is None:
|
||||
self._channel = Channel(LinkChannelOutlet(self))
|
||||
return self._channel
|
||||
|
||||
def receive(self, packet):
|
||||
self.watchdog_lock = True
|
||||
if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])):
|
||||
@@ -637,123 +746,148 @@ class Link:
|
||||
self.status = Link.ACTIVE
|
||||
|
||||
if packet.packet_type == RNS.Packet.DATA:
|
||||
should_query = False
|
||||
if packet.context == RNS.Packet.NONE:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
if self.callbacks.packet != None:
|
||||
thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
if self.destination.proof_strategy == RNS.Destination.PROVE_ALL:
|
||||
packet.prove()
|
||||
if plaintext != None:
|
||||
if self.callbacks.packet != None:
|
||||
thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
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:
|
||||
self.destination.callbacks.proof_requested(packet)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
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:
|
||||
if not self.initiator and len(plaintext) == RNS.Identity.KEYSIZE//8 + RNS.Identity.SIGLENGTH//8:
|
||||
public_key = plaintext[:RNS.Identity.KEYSIZE//8]
|
||||
signed_data = self.link_id+public_key
|
||||
signature = plaintext[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.SIGLENGTH//8]
|
||||
identity = RNS.Identity(create_keys=False)
|
||||
identity.load_public_key(public_key)
|
||||
|
||||
if not self.initiator and len(plaintext) == RNS.Identity.KEYSIZE//8 + RNS.Identity.SIGLENGTH//8:
|
||||
public_key = plaintext[:RNS.Identity.KEYSIZE//8]
|
||||
signed_data = self.link_id+public_key
|
||||
signature = plaintext[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.SIGLENGTH//8]
|
||||
identity = RNS.Identity(create_keys=False)
|
||||
identity.load_public_key(public_key)
|
||||
|
||||
if identity.validate(signature, signed_data):
|
||||
self.__remote_identity = identity
|
||||
if self.callbacks.remote_identified != None:
|
||||
try:
|
||||
self.callbacks.remote_identified(self, self.__remote_identity)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if identity.validate(signature, signed_data):
|
||||
self.__remote_identity = identity
|
||||
if self.callbacks.remote_identified != None:
|
||||
try:
|
||||
self.callbacks.remote_identified(self, self.__remote_identity)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
elif packet.context == RNS.Packet.REQUEST:
|
||||
try:
|
||||
request_id = packet.getTruncatedHash()
|
||||
packed_request = self.decrypt(packet.data)
|
||||
unpacked_request = umsgpack.unpackb(packed_request)
|
||||
self.handle_request(request_id, unpacked_request)
|
||||
if packed_request != None:
|
||||
unpacked_request = umsgpack.unpackb(packed_request)
|
||||
self.handle_request(request_id, unpacked_request)
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
elif packet.context == RNS.Packet.RESPONSE:
|
||||
try:
|
||||
packed_response = self.decrypt(packet.data)
|
||||
unpacked_response = umsgpack.unpackb(packed_response)
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
transfer_size = len(umsgpack.packb(response_data))-2
|
||||
self.handle_response(request_id, response_data, transfer_size, transfer_size)
|
||||
if packed_response != None:
|
||||
unpacked_response = umsgpack.unpackb(packed_response)
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
transfer_size = len(umsgpack.packb(response_data))-2
|
||||
self.handle_response(request_id, response_data, transfer_size, transfer_size)
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
elif packet.context == RNS.Packet.LRRTT:
|
||||
if not self.initiator:
|
||||
self.rtt_packet(packet)
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
elif packet.context == RNS.Packet.LINKCLOSE:
|
||||
self.teardown_packet(packet)
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_ADV:
|
||||
packet.plaintext = self.decrypt(packet.data)
|
||||
if packet.plaintext != None:
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
if RNS.ResourceAdvertisement.is_request(packet):
|
||||
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
||||
elif RNS.ResourceAdvertisement.is_response(packet):
|
||||
request_id = RNS.ResourceAdvertisement.read_request_id(packet)
|
||||
for pending_request in self.pending_requests:
|
||||
if pending_request.request_id == request_id:
|
||||
RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
|
||||
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
|
||||
pending_request.response_transfer_size = RNS.ResourceAdvertisement.read_transfer_size(packet)
|
||||
pending_request.started_at = time.time()
|
||||
elif self.resource_strategy == Link.ACCEPT_NONE:
|
||||
pass
|
||||
elif self.resource_strategy == Link.ACCEPT_APP:
|
||||
if self.callbacks.resource != None:
|
||||
try:
|
||||
resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext)
|
||||
resource_advertisement.link = self
|
||||
if self.callbacks.resource(resource_advertisement):
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
elif self.resource_strategy == Link.ACCEPT_ALL:
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
if RNS.ResourceAdvertisement.is_request(packet):
|
||||
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
||||
elif RNS.ResourceAdvertisement.is_response(packet):
|
||||
request_id = RNS.ResourceAdvertisement.read_request_id(packet)
|
||||
for pending_request in self.pending_requests:
|
||||
if pending_request.request_id == request_id:
|
||||
response_resource = RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
|
||||
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
|
||||
pending_request.response_transfer_size = RNS.ResourceAdvertisement.read_transfer_size(packet)
|
||||
pending_request.started_at = time.time()
|
||||
pending_request.response_resource_progress(response_resource)
|
||||
|
||||
elif self.resource_strategy == Link.ACCEPT_NONE:
|
||||
pass
|
||||
elif self.resource_strategy == Link.ACCEPT_APP:
|
||||
if self.callbacks.resource != None:
|
||||
try:
|
||||
resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext)
|
||||
resource_advertisement.link = self
|
||||
if self.callbacks.resource(resource_advertisement):
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
elif self.resource_strategy == Link.ACCEPT_ALL:
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
|
||||
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN]
|
||||
else:
|
||||
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH//8+1]
|
||||
if plaintext != None:
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
|
||||
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN]
|
||||
else:
|
||||
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH//8+1]
|
||||
|
||||
for resource in self.outgoing_resources:
|
||||
if resource.hash == resource_hash:
|
||||
# We need to check that this request has not been
|
||||
# received before in order to avoid sequencing errors.
|
||||
if not packet.packet_hash in resource.req_hashlist:
|
||||
resource.req_hashlist.append(packet.packet_hash)
|
||||
resource.request(plaintext)
|
||||
for resource in self.outgoing_resources:
|
||||
if resource.hash == resource_hash:
|
||||
# We need to check that this request has not been
|
||||
# received before in order to avoid sequencing errors.
|
||||
if not packet.packet_hash in resource.req_hashlist:
|
||||
resource.req_hashlist.append(packet.packet_hash)
|
||||
resource.request(plaintext)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
|
||||
for resource in self.incoming_resources:
|
||||
if resource_hash == resource.hash:
|
||||
resource.hashmap_update_packet(plaintext)
|
||||
if plaintext != None:
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
|
||||
for resource in self.incoming_resources:
|
||||
if resource_hash == resource.hash:
|
||||
resource.hashmap_update_packet(plaintext)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_ICL:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
|
||||
for resource in self.incoming_resources:
|
||||
if resource_hash == resource.hash:
|
||||
resource.cancel()
|
||||
if plaintext != None:
|
||||
self.__update_phy_stats(packet)
|
||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
|
||||
for resource in self.incoming_resources:
|
||||
if resource_hash == resource.hash:
|
||||
resource.cancel()
|
||||
|
||||
elif packet.context == RNS.Packet.KEEPALIVE:
|
||||
if not self.initiator and packet.data == bytes([0xFF]):
|
||||
@@ -769,6 +903,17 @@ class Link:
|
||||
elif packet.context == RNS.Packet.RESOURCE:
|
||||
for resource in self.incoming_resources:
|
||||
resource.receive_part(packet)
|
||||
self.__update_phy_stats(packet)
|
||||
|
||||
elif packet.context == RNS.Packet.CHANNEL:
|
||||
if not self._channel:
|
||||
RNS.log(f"Channel data received without open channel", RNS.LOG_DEBUG)
|
||||
else:
|
||||
packet.prove()
|
||||
plaintext = self.decrypt(packet.data)
|
||||
if plaintext != None:
|
||||
self.__update_phy_stats(packet)
|
||||
self._channel._receive(plaintext)
|
||||
|
||||
elif packet.packet_type == RNS.Packet.PROOF:
|
||||
if packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
@@ -776,6 +921,7 @@ class Link:
|
||||
for resource in self.outgoing_resources:
|
||||
if resource_hash == resource.hash:
|
||||
resource.validate_proof(packet.data)
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
self.watchdog_lock = False
|
||||
|
||||
@@ -786,7 +932,7 @@ class Link:
|
||||
try:
|
||||
self.fernet = Fernet(self.derived_key)
|
||||
except Exception as e:
|
||||
RNS.log("Could not "+str(self)+" instantiate Fernet while performin encryption on link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Could not instantiate Fernet while performin encryption on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
return self.fernet.encrypt(plaintext)
|
||||
@@ -805,6 +951,7 @@ class Link:
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
|
||||
def sign(self, message):
|
||||
@@ -1029,11 +1176,12 @@ class RequestReceipt():
|
||||
if not self.status == RequestReceipt.FAILED:
|
||||
self.status = RequestReceipt.RECEIVING
|
||||
if self.packet_receipt != None:
|
||||
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
|
||||
self.packet_receipt.proved = True
|
||||
self.packet_receipt.concluded_at = time.time()
|
||||
if self.packet_receipt.callbacks.delivery != None:
|
||||
self.packet_receipt.callbacks.delivery(self.packet_receipt)
|
||||
if self.packet_receipt.status != RNS.PacketReceipt.DELIVERED:
|
||||
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
|
||||
self.packet_receipt.proved = True
|
||||
self.packet_receipt.concluded_at = time.time()
|
||||
if self.packet_receipt.callbacks.delivery != None:
|
||||
self.packet_receipt.callbacks.delivery(self.packet_receipt)
|
||||
|
||||
self.progress = resource.get_progress()
|
||||
|
||||
|
||||
+20
-8
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -30,7 +30,7 @@ class Packet:
|
||||
"""
|
||||
The Packet class is used to create packet instances that can be sent
|
||||
over a Reticulum network. Packets will automatically be encrypted if
|
||||
they are adressed to a ``RNS.Destination.SINGLE`` destination,
|
||||
they are addressed to a ``RNS.Destination.SINGLE`` destination,
|
||||
``RNS.Destination.GROUP`` destination or a :ref:`RNS.Link<api-link>`.
|
||||
|
||||
For ``RNS.Destination.GROUP`` destinations, Reticulum will use the
|
||||
@@ -75,6 +75,7 @@ class Packet:
|
||||
PATH_RESPONSE = 0x0B # Packet is a response to a path request
|
||||
COMMAND = 0x0C # Packet is a command
|
||||
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
|
||||
CHANNEL = 0x0E # Packet contains link channel data
|
||||
KEEPALIVE = 0xFA # Packet is a keepalive packet
|
||||
LINKIDENTIFY = 0xFB # Packet is a link peer identification proof
|
||||
LINKCLOSE = 0xFC # Packet is a link close message
|
||||
@@ -137,10 +138,11 @@ class Packet:
|
||||
self.receiving_interface = None
|
||||
self.rssi = None
|
||||
self.snr = None
|
||||
self.q = None
|
||||
|
||||
def get_packed_flags(self):
|
||||
if self.context == Packet.LRPROOF:
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | RNS.Destination.LINK | self.packet_type
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
|
||||
else:
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
|
||||
return packed_flags
|
||||
@@ -171,8 +173,8 @@ class Packet:
|
||||
# Packet proofs over links are not encrypted
|
||||
self.ciphertext = self.data
|
||||
elif self.context == Packet.RESOURCE:
|
||||
# A resource takes care of symmetric
|
||||
# encryption by itself
|
||||
# A resource takes care of encryption
|
||||
# by itself
|
||||
self.ciphertext = self.data
|
||||
elif self.context == Packet.KEEPALIVE:
|
||||
# Keepalive packets contain no actual
|
||||
@@ -275,6 +277,10 @@ class Packet:
|
||||
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
|
||||
"""
|
||||
if self.sent:
|
||||
# Re-pack the packet to obtain new ciphertext for
|
||||
# encrypted destinations
|
||||
self.pack()
|
||||
|
||||
if RNS.Transport.outbound(self):
|
||||
return self.receipt
|
||||
else:
|
||||
@@ -365,8 +371,8 @@ class PacketReceipt:
|
||||
if packet.destination.type == RNS.Destination.LINK:
|
||||
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
|
||||
else:
|
||||
self.timeout = Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
||||
|
||||
self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
|
||||
self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
@@ -395,9 +401,15 @@ class PacketReceipt:
|
||||
self.proved = True
|
||||
self.concluded_at = time.time()
|
||||
self.proof_packet = proof_packet
|
||||
link.last_proof = self.concluded_at
|
||||
|
||||
if self.callbacks.delivery != None:
|
||||
self.callbacks.delivery(self)
|
||||
try:
|
||||
self.callbacks.delivery(self)
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
class Resolver:
|
||||
|
||||
@staticmethod
|
||||
def resolve_identity(full_name):
|
||||
pass
|
||||
+106
-91
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -26,6 +26,7 @@ import bz2
|
||||
import math
|
||||
import time
|
||||
import threading
|
||||
from threading import Lock
|
||||
from .vendor import umsgpack as umsgpack
|
||||
from time import sleep
|
||||
|
||||
@@ -47,7 +48,7 @@ class Resource:
|
||||
WINDOW = 4
|
||||
|
||||
# Absolute minimum window size during transfer
|
||||
WINDOW_MIN = 1
|
||||
WINDOW_MIN = 2
|
||||
|
||||
# The maximum window size for transfers on slow links
|
||||
WINDOW_MAX_SLOW = 10
|
||||
@@ -103,7 +104,7 @@ class Resource:
|
||||
|
||||
PART_TIMEOUT_FACTOR = 4
|
||||
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
|
||||
MAX_RETRIES = 8
|
||||
MAX_RETRIES = 16
|
||||
MAX_ADV_RETRIES = 4
|
||||
SENDER_GRACE_TIME = 10
|
||||
RETRY_GRACE_TIME = 0.25
|
||||
@@ -149,7 +150,7 @@ class Resource:
|
||||
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
|
||||
resource.parts = [None] * resource.total_parts
|
||||
resource.window = Resource.WINDOW
|
||||
resource.window_max = Resource.WINDOW_MAX_SLOW
|
||||
resource.window_min = Resource.WINDOW_MIN
|
||||
@@ -167,15 +168,13 @@ class Resource:
|
||||
resource.hashmap = [None] * resource.total_parts
|
||||
resource.hashmap_height = 0
|
||||
resource.waiting_for_hmu = False
|
||||
|
||||
resource.receiving_part = False
|
||||
|
||||
resource.consecutive_completed_height = 0
|
||||
resource.consecutive_completed_height = -1
|
||||
|
||||
if not resource.link.has_incoming_resource(resource):
|
||||
resource.link.register_incoming_resource(resource)
|
||||
|
||||
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
|
||||
RNS.log(f"Accepting resource advertisement for {RNS.prettyhexrep(resource.hash)}. Transfer size is {RNS.prettysize(resource.size)} in {resource.total_parts} parts.", RNS.LOG_DEBUG)
|
||||
if resource.link.callbacks.resource_started != None:
|
||||
try:
|
||||
resource.link.callbacks.resource_started(resource)
|
||||
@@ -366,7 +365,8 @@ class Resource:
|
||||
if advertise:
|
||||
self.advertise()
|
||||
else:
|
||||
pass
|
||||
self.receive_lock = Lock()
|
||||
|
||||
|
||||
def hashmap_update_packet(self, plaintext):
|
||||
if not self.status == Resource.FAILED:
|
||||
@@ -476,7 +476,8 @@ class Resource:
|
||||
|
||||
if sleep_time < 0:
|
||||
if self.retries_left > 0:
|
||||
RNS.log("Timed out waiting for parts, requesting retry", RNS.LOG_DEBUG)
|
||||
ms = "" if self.outstanding_parts == 1 else "s"
|
||||
RNS.log("Timed out waiting for "+str(self.outstanding_parts)+" part"+ms+", requesting retry", RNS.LOG_DEBUG)
|
||||
if self.window > self.window_min:
|
||||
self.window -= 1
|
||||
if self.window_max > self.window_min:
|
||||
@@ -622,98 +623,99 @@ class Resource:
|
||||
|
||||
|
||||
def receive_part(self, packet):
|
||||
while self.receiving_part:
|
||||
sleep(0.001)
|
||||
with self.receive_lock:
|
||||
|
||||
self.receiving_part = True
|
||||
self.last_activity = time.time()
|
||||
self.retries_left = self.max_retries
|
||||
self.receiving_part = True
|
||||
self.last_activity = time.time()
|
||||
self.retries_left = self.max_retries
|
||||
|
||||
if self.req_resp == None:
|
||||
self.req_resp = self.last_activity
|
||||
rtt = self.req_resp-self.req_sent
|
||||
|
||||
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR_AFTER_RTT
|
||||
if self.rtt == None:
|
||||
self.rtt = self.link.rtt
|
||||
self.watchdog_job()
|
||||
elif rtt < self.rtt:
|
||||
self.rtt = max(self.rtt - self.rtt*0.05, rtt)
|
||||
elif rtt > self.rtt:
|
||||
self.rtt = min(self.rtt + self.rtt*0.05, rtt)
|
||||
if self.req_resp == None:
|
||||
self.req_resp = self.last_activity
|
||||
rtt = self.req_resp-self.req_sent
|
||||
|
||||
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR_AFTER_RTT
|
||||
if self.rtt == None:
|
||||
self.rtt = self.link.rtt
|
||||
self.watchdog_job()
|
||||
elif rtt < self.rtt:
|
||||
self.rtt = max(self.rtt - self.rtt*0.05, rtt)
|
||||
elif rtt > self.rtt:
|
||||
self.rtt = min(self.rtt + self.rtt*0.05, rtt)
|
||||
|
||||
if rtt > 0:
|
||||
req_resp_cost = len(packet.raw)+self.req_sent_bytes
|
||||
self.req_resp_rtt_rate = req_resp_cost / rtt
|
||||
if rtt > 0:
|
||||
req_resp_cost = len(packet.raw)+self.req_sent_bytes
|
||||
self.req_resp_rtt_rate = req_resp_cost / rtt
|
||||
|
||||
if self.req_resp_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
|
||||
self.fast_rate_rounds += 1
|
||||
if self.req_resp_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
|
||||
self.fast_rate_rounds += 1
|
||||
|
||||
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Resource.WINDOW_MAX_FAST
|
||||
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Resource.WINDOW_MAX_FAST
|
||||
|
||||
if not self.status == Resource.FAILED:
|
||||
self.status = Resource.TRANSFERRING
|
||||
part_data = packet.data
|
||||
part_hash = self.get_map_hash(part_data)
|
||||
if not self.status == Resource.FAILED:
|
||||
self.status = Resource.TRANSFERRING
|
||||
part_data = packet.data
|
||||
part_hash = self.get_map_hash(part_data)
|
||||
|
||||
i = self.consecutive_completed_height
|
||||
for map_hash in self.hashmap[self.consecutive_completed_height:self.consecutive_completed_height+self.window]:
|
||||
if map_hash == part_hash:
|
||||
if self.parts[i] == None:
|
||||
# Insert data into parts list
|
||||
self.parts[i] = part_data
|
||||
self.rtt_rxd_bytes += len(part_data)
|
||||
self.received_count += 1
|
||||
self.outstanding_parts -= 1
|
||||
consecutive_index = self.consecutive_completed_height if self.consecutive_completed_height >= 0 else 0
|
||||
i = consecutive_index
|
||||
for map_hash in self.hashmap[consecutive_index:consecutive_index+self.window]:
|
||||
if map_hash == part_hash:
|
||||
if self.parts[i] == None:
|
||||
|
||||
# Update consecutive completed pointer
|
||||
if i == self.consecutive_completed_height + 1:
|
||||
self.consecutive_completed_height = i
|
||||
|
||||
cp = self.consecutive_completed_height + 1
|
||||
while cp < len(self.parts) and self.parts[cp] != None:
|
||||
self.consecutive_completed_height = cp
|
||||
cp += 1
|
||||
# Insert data into parts list
|
||||
self.parts[i] = part_data
|
||||
self.rtt_rxd_bytes += len(part_data)
|
||||
self.received_count += 1
|
||||
self.outstanding_parts -= 1
|
||||
|
||||
if self.__progress_callback != None:
|
||||
try:
|
||||
self.__progress_callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
# Update consecutive completed pointer
|
||||
if i == self.consecutive_completed_height + 1:
|
||||
self.consecutive_completed_height = i
|
||||
|
||||
cp = self.consecutive_completed_height + 1
|
||||
while cp < len(self.parts) and self.parts[cp] != None:
|
||||
self.consecutive_completed_height = cp
|
||||
cp += 1
|
||||
|
||||
i += 1
|
||||
if self.__progress_callback != None:
|
||||
try:
|
||||
self.__progress_callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
self.receiving_part = False
|
||||
i += 1
|
||||
|
||||
if self.received_count == self.total_parts and not self.assembly_lock:
|
||||
self.assembly_lock = True
|
||||
self.assemble()
|
||||
elif self.outstanding_parts == 0:
|
||||
# TODO: Figure out if there is a mathematically
|
||||
# optimal way to adjust windows
|
||||
if self.window < self.window_max:
|
||||
self.window += 1
|
||||
if (self.window - self.window_min) > (self.window_flexibility-1):
|
||||
self.window_min += 1
|
||||
self.receiving_part = False
|
||||
|
||||
if self.req_sent != 0:
|
||||
rtt = time.time()-self.req_sent
|
||||
req_transferred = self.rtt_rxd_bytes - self.rtt_rxd_bytes_at_part_req
|
||||
if self.received_count == self.total_parts and not self.assembly_lock:
|
||||
self.assembly_lock = True
|
||||
self.assemble()
|
||||
elif self.outstanding_parts == 0:
|
||||
# TODO: Figure out if there is a mathematically
|
||||
# optimal way to adjust windows
|
||||
if self.window < self.window_max:
|
||||
self.window += 1
|
||||
if (self.window - self.window_min) > (self.window_flexibility-1):
|
||||
self.window_min += 1
|
||||
|
||||
if rtt != 0:
|
||||
self.req_data_rtt_rate = req_transferred/rtt
|
||||
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
|
||||
if self.req_sent != 0:
|
||||
rtt = time.time()-self.req_sent
|
||||
req_transferred = self.rtt_rxd_bytes - self.rtt_rxd_bytes_at_part_req
|
||||
|
||||
if self.req_data_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
|
||||
self.fast_rate_rounds += 1
|
||||
if rtt != 0:
|
||||
self.req_data_rtt_rate = req_transferred/rtt
|
||||
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
|
||||
|
||||
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Resource.WINDOW_MAX_FAST
|
||||
if self.req_data_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
|
||||
self.fast_rate_rounds += 1
|
||||
|
||||
self.request_next()
|
||||
else:
|
||||
self.receiving_part = False
|
||||
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Resource.WINDOW_MAX_FAST
|
||||
|
||||
self.request_next()
|
||||
else:
|
||||
self.receiving_part = False
|
||||
|
||||
# Called on incoming resource to send a request for more data
|
||||
def request_next(self):
|
||||
@@ -726,11 +728,25 @@ class Resource:
|
||||
hashmap_exhausted = Resource.HASHMAP_IS_NOT_EXHAUSTED
|
||||
requested_hashes = b""
|
||||
|
||||
offset = (1 if self.consecutive_completed_height > 0 else 0)
|
||||
i = 0; pn = self.consecutive_completed_height+offset
|
||||
i = 0; pn = self.consecutive_completed_height+1
|
||||
search_start = pn
|
||||
search_size = self.window
|
||||
|
||||
for part in self.parts[search_start:search_start+self.window]:
|
||||
# TODO: Remove
|
||||
# tpm = []
|
||||
# tpi = 0
|
||||
# try:
|
||||
# for p in self.parts:
|
||||
# if p == None:
|
||||
# tpm.append(None)
|
||||
# else:
|
||||
# tpm.append(tpi)
|
||||
# tpi+=1
|
||||
# except Exception as e:
|
||||
# print(str(e))
|
||||
# RNS.log(f"Partmap: "+str(tpm))
|
||||
|
||||
for part in self.parts[search_start:search_start+search_size]:
|
||||
if part == None:
|
||||
part_hash = self.hashmap[pn]
|
||||
if part_hash != None:
|
||||
@@ -750,7 +766,6 @@ class Resource:
|
||||
hmu_part += last_map_hash
|
||||
self.waiting_for_hmu = True
|
||||
|
||||
requested_data = b""
|
||||
request_data = hmu_part + self.hash + requested_hashes
|
||||
request_packet = RNS.Packet(self.link, request_data, context = RNS.Packet.RESOURCE_REQ)
|
||||
|
||||
@@ -760,6 +775,7 @@ class Resource:
|
||||
self.req_sent = self.last_activity
|
||||
self.req_sent_bytes = len(request_packet.raw)
|
||||
self.req_resp = None
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not send resource request packet, cancelling resource", RNS.LOG_DEBUG)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
||||
@@ -905,8 +921,7 @@ class Resource:
|
||||
else:
|
||||
self.progress_total_parts = float(self.total_parts)
|
||||
|
||||
|
||||
progress = self.processed_parts / self.progress_total_parts
|
||||
progress = min(1.0, self.processed_parts / self.progress_total_parts)
|
||||
return progress
|
||||
|
||||
def get_transfer_size(self):
|
||||
|
||||
+176
-16
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -79,7 +79,7 @@ class Reticulum:
|
||||
MTU = 500
|
||||
"""
|
||||
The MTU that Reticulum adheres to, and will expect other peers to
|
||||
adhere to. By default, the MTU is 507 bytes. In custom RNS network
|
||||
adhere to. By default, the MTU is 500 bytes. In custom RNS network
|
||||
implementations, it is possible to change this value, but doing so will
|
||||
completely break compatibility with all other RNS networks. An identical
|
||||
MTU is a prerequisite for peers to communicate in the same network.
|
||||
@@ -106,18 +106,20 @@ class Reticulum:
|
||||
it will eventually be dropped.
|
||||
|
||||
This value will be applied by default to all created interfaces,
|
||||
but it can be configured individually on a per-interface basis.
|
||||
but it can be configured individually on a per-interface basis. In
|
||||
general, the global default setting should not be changed, and any
|
||||
alterations should be made on a per-interface basis instead.
|
||||
"""
|
||||
|
||||
MINIMUM_BITRATE = 500
|
||||
MINIMUM_BITRATE = 5
|
||||
"""
|
||||
Minimum bitrate required across a medium for Reticulum to be able
|
||||
to successfully establish links. Currently 5 bits per second.
|
||||
"""
|
||||
|
||||
# TODO: To reach the 300bps level without unreasonably impacting
|
||||
# performance on faster links, we need a mechanism for setting
|
||||
# this value more intelligently. One option could be inferring it
|
||||
# from interface speed, but a better general approach would most
|
||||
# probably be to let Reticulum somehow continously build a map of
|
||||
# per-hop latencies and use this map for the timeout calculation.
|
||||
DEFAULT_PER_HOP_TIMEOUT = 5
|
||||
# TODO: Let Reticulum somehow continously build a map of per-hop
|
||||
# latencies and use this map for global timeout calculation.
|
||||
DEFAULT_PER_HOP_TIMEOUT = 6
|
||||
|
||||
# Length of truncated hashes in bits.
|
||||
TRUNCATED_HASHLENGTH = 128
|
||||
@@ -133,6 +135,7 @@ class Reticulum:
|
||||
JOB_INTERVAL = 5*60
|
||||
CLEAN_INTERVAL = 15*60
|
||||
PERSIST_INTERVAL = 60*60*12
|
||||
GRACIOUS_PERSIST_INTERVAL = 60*5
|
||||
|
||||
router = None
|
||||
config = None
|
||||
@@ -144,6 +147,8 @@ class Reticulum:
|
||||
configpath = ""
|
||||
storagepath = ""
|
||||
cachepath = ""
|
||||
|
||||
__instance = None
|
||||
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
@@ -167,7 +172,14 @@ class Reticulum:
|
||||
RNS.exit()
|
||||
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None):
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
"""
|
||||
Return the currently running Reticulum instance
|
||||
"""
|
||||
return Reticulum.__instance
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
|
||||
"""
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
@@ -176,6 +188,11 @@ class Reticulum:
|
||||
:param configdir: Full path to a Reticulum configuration directory.
|
||||
"""
|
||||
|
||||
if Reticulum.__instance != None:
|
||||
raise OSError("Attempt to reinitialise Reticulum, when it was already running")
|
||||
else:
|
||||
Reticulum.__instance = self
|
||||
|
||||
RNS.vendor.platformutils.platform_checks()
|
||||
|
||||
if configdir != None:
|
||||
@@ -200,6 +217,7 @@ class Reticulum:
|
||||
|
||||
Reticulum.__transport_enabled = False
|
||||
Reticulum.__use_implicit_proof = True
|
||||
Reticulum.__allow_probes = False
|
||||
|
||||
Reticulum.panic_on_interface_error = False
|
||||
|
||||
@@ -207,10 +225,12 @@ class Reticulum:
|
||||
self.local_control_port = 37429
|
||||
self.share_instance = True
|
||||
self.rpc_listener = None
|
||||
self.rpc_key = None
|
||||
|
||||
self.ifac_salt = Reticulum.IFAC_SALT
|
||||
|
||||
self.requested_loglevel = loglevel
|
||||
self.requested_verbosity = verbosity
|
||||
if self.requested_loglevel != None:
|
||||
if self.requested_loglevel > RNS.LOG_EXTREME:
|
||||
self.requested_loglevel = RNS.LOG_EXTREME
|
||||
@@ -259,7 +279,8 @@ class Reticulum:
|
||||
RNS.Transport.start(self)
|
||||
|
||||
self.rpc_addr = ("127.0.0.1", self.local_control_port)
|
||||
self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key())
|
||||
if self.rpc_key == None:
|
||||
self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key())
|
||||
|
||||
if self.is_shared_instance:
|
||||
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key)
|
||||
@@ -287,7 +308,6 @@ class Reticulum:
|
||||
|
||||
if now > self.last_data_persist+Reticulum.PERSIST_INTERVAL:
|
||||
self.__persist_data()
|
||||
self.last_data_persist = time.time()
|
||||
|
||||
time.sleep(Reticulum.JOB_INTERVAL)
|
||||
|
||||
@@ -299,6 +319,10 @@ class Reticulum:
|
||||
self.local_interface_port
|
||||
)
|
||||
interface.OUT = True
|
||||
if hasattr(Reticulum, "_force_shared_instance_bitrate"):
|
||||
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)
|
||||
|
||||
self.is_shared_instance = True
|
||||
@@ -313,11 +337,16 @@ class Reticulum:
|
||||
self.local_interface_port)
|
||||
interface.target_port = self.local_interface_port
|
||||
interface.OUT = True
|
||||
if hasattr(Reticulum, "_force_shared_instance_bitrate"):
|
||||
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)
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
self.is_shared_instance = False
|
||||
self.is_standalone_instance = False
|
||||
self.is_connected_to_shared_instance = True
|
||||
Reticulum.__transport_enabled = False
|
||||
Reticulum.__allow_probes = False
|
||||
RNS.log("Connected to locally available Reticulum instance via: "+str(interface), RNS.LOG_DEBUG)
|
||||
except Exception as e:
|
||||
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
|
||||
@@ -337,6 +366,8 @@ class Reticulum:
|
||||
value = self.config["logging"][option]
|
||||
if option == "loglevel" and self.requested_loglevel == None:
|
||||
RNS.loglevel = int(value)
|
||||
if self.requested_verbosity != None:
|
||||
RNS.loglevel += self.requested_verbosity
|
||||
if RNS.loglevel < 0:
|
||||
RNS.loglevel = 0
|
||||
if RNS.loglevel > 7:
|
||||
@@ -354,10 +385,24 @@ class Reticulum:
|
||||
if option == "instance_control_port":
|
||||
value = int(self.config["reticulum"][option])
|
||||
self.local_control_port = value
|
||||
if option == "rpc_key":
|
||||
try:
|
||||
value = bytes.fromhex(self.config["reticulum"][option])
|
||||
self.rpc_key = value
|
||||
except Exception as e:
|
||||
RNS.log("Invalid shared instance RPC key specified, falling back to default key", RNS.LOG_ERROR)
|
||||
self.rpc_key = None
|
||||
if option == "enable_transport":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__transport_enabled = True
|
||||
if option == "respond_to_probes":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__allow_probes = True
|
||||
if option == "force_shared_instance_bitrate":
|
||||
v = self.config["reticulum"].as_int(option)
|
||||
Reticulum._force_shared_instance_bitrate = v
|
||||
if option == "panic_on_interface_error":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
@@ -438,6 +483,23 @@ class Reticulum:
|
||||
if c["pass_phrase"] != "":
|
||||
ifac_netkey = c["pass_phrase"]
|
||||
|
||||
ingress_control = True
|
||||
if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
|
||||
ic_max_held_announces = None
|
||||
if "ic_max_held_announces" in c: ic_max_held_announces = c.as_int("ic_max_held_announces")
|
||||
ic_burst_hold = None
|
||||
if "ic_burst_hold" in c: ic_burst_hold = c.as_float("ic_burst_hold")
|
||||
ic_burst_freq_new = None
|
||||
if "ic_burst_freq_new" in c: ic_burst_freq_new = c.as_float("ic_burst_freq_new")
|
||||
ic_burst_freq = None
|
||||
if "ic_burst_freq" in c: ic_burst_freq = c.as_float("ic_burst_freq")
|
||||
ic_new_time = None
|
||||
if "ic_new_time" in c: ic_new_time = c.as_float("ic_new_time")
|
||||
ic_burst_penalty = None
|
||||
if "ic_burst_penalty" in c: ic_burst_penalty = c.as_float("ic_burst_penalty")
|
||||
ic_held_release_interval = None
|
||||
if "ic_held_release_interval" in c: ic_held_release_interval = c.as_float("ic_held_release_interval")
|
||||
|
||||
configured_bitrate = None
|
||||
if "bitrate" in c:
|
||||
if c.as_int("bitrate") >= Reticulum.MINIMUM_BITRATE:
|
||||
@@ -832,6 +894,8 @@ class Reticulum:
|
||||
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
|
||||
|
||||
port = c["port"] if "port" in c else None
|
||||
|
||||
@@ -849,7 +913,9 @@ class Reticulum:
|
||||
cr = codingrate,
|
||||
flow_control = flow_control,
|
||||
id_interval = id_interval,
|
||||
id_callsign = id_callsign
|
||||
id_callsign = id_callsign,
|
||||
st_alock = st_alock,
|
||||
lt_alock = lt_alock
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
@@ -871,6 +937,14 @@ class Reticulum:
|
||||
interface.announce_rate_target = announce_rate_target
|
||||
interface.announce_rate_grace = announce_rate_grace
|
||||
interface.announce_rate_penalty = announce_rate_penalty
|
||||
interface.ingress_control = ingress_control
|
||||
if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces
|
||||
if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
|
||||
if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new
|
||||
if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq
|
||||
if ic_new_time != None: interface.ic_new_time = ic_new_time
|
||||
if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty
|
||||
if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval
|
||||
|
||||
interface.ifac_netname = ifac_netname
|
||||
interface.ifac_netkey = ifac_netkey
|
||||
@@ -957,11 +1031,13 @@ class Reticulum:
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
def _should_persist_data(self):
|
||||
self.__persist_data()
|
||||
if time.time() > self.last_data_persist+Reticulum.GRACIOUS_PERSIST_INTERVAL:
|
||||
self.__persist_data()
|
||||
|
||||
def __persist_data(self):
|
||||
RNS.Transport.persist_data()
|
||||
RNS.Identity.persist_data()
|
||||
self.last_data_persist = time.time()
|
||||
|
||||
def __clean_caches(self):
|
||||
RNS.log("Cleaning resource and packet caches...", RNS.LOG_EXTREME)
|
||||
@@ -1025,18 +1101,27 @@ class Reticulum:
|
||||
if path == "next_hop":
|
||||
rpc_connection.send(self.get_next_hop(call["destination_hash"]))
|
||||
|
||||
if path == "first_hop_timeout":
|
||||
rpc_connection.send(self.get_first_hop_timeout(call["destination_hash"]))
|
||||
|
||||
if path == "packet_rssi":
|
||||
rpc_connection.send(self.get_packet_rssi(call["packet_hash"]))
|
||||
|
||||
if path == "packet_snr":
|
||||
rpc_connection.send(self.get_packet_snr(call["packet_hash"]))
|
||||
|
||||
if path == "packet_q":
|
||||
rpc_connection.send(self.get_packet_q(call["packet_hash"]))
|
||||
|
||||
if "drop" in call:
|
||||
path = call["drop"]
|
||||
|
||||
if path == "path":
|
||||
rpc_connection.send(self.drop_path(call["destination_hash"]))
|
||||
|
||||
if path == "all_via":
|
||||
rpc_connection.send(self.drop_all_via(call["destination_hash"]))
|
||||
|
||||
if path == "announce_queues":
|
||||
rpc_connection.send(self.drop_announce_queues())
|
||||
|
||||
@@ -1086,6 +1171,18 @@ class Reticulum:
|
||||
else:
|
||||
ifstats["tunnelstate"] = None
|
||||
|
||||
if hasattr(interface, "r_airtime_short"):
|
||||
ifstats["airtime_short"] = interface.r_airtime_short
|
||||
|
||||
if hasattr(interface, "r_airtime_long"):
|
||||
ifstats["airtime_long"] = interface.r_airtime_long
|
||||
|
||||
if hasattr(interface, "r_channel_load_short"):
|
||||
ifstats["channel_load_short"] = interface.r_channel_load_short
|
||||
|
||||
if hasattr(interface, "r_channel_load_long"):
|
||||
ifstats["channel_load_long"] = interface.r_channel_load_long
|
||||
|
||||
if hasattr(interface, "bitrate"):
|
||||
if interface.bitrate != None:
|
||||
ifstats["bitrate"] = interface.bitrate
|
||||
@@ -1116,6 +1213,9 @@ class Reticulum:
|
||||
ifstats["name"] = str(interface)
|
||||
ifstats["rxb"] = interface.rxb
|
||||
ifstats["txb"] = interface.txb
|
||||
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
|
||||
ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
|
||||
ifstats["held_announces"] = len(interface.held_announces)
|
||||
ifstats["status"] = interface.online
|
||||
ifstats["mode"] = interface.mode
|
||||
|
||||
@@ -1125,6 +1225,11 @@ class Reticulum:
|
||||
stats["interfaces"] = interfaces
|
||||
if Reticulum.transport_enabled():
|
||||
stats["transport_id"] = RNS.Transport.identity.hash
|
||||
stats["transport_uptime"] = time.time()-RNS.Transport.start_time
|
||||
if Reticulum.probe_destination_enabled():
|
||||
stats["probe_responder"] = RNS.Transport.probe_destination.hash
|
||||
else:
|
||||
stats["probe_responder"] = None
|
||||
|
||||
return stats
|
||||
|
||||
@@ -1181,6 +1286,22 @@ class Reticulum:
|
||||
else:
|
||||
return RNS.Transport.expire_path(destination)
|
||||
|
||||
def drop_all_via(self, transport_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"drop": "all_via", "destination_hash": transport_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else:
|
||||
dropped_count = 0
|
||||
for destination_hash in RNS.Transport.destination_table:
|
||||
if RNS.Transport.destination_table[destination_hash][1] == transport_hash:
|
||||
RNS.Transport.expire_path(destination_hash)
|
||||
dropped_count += 1
|
||||
|
||||
return dropped_count
|
||||
|
||||
def drop_announce_queues(self):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
@@ -1201,6 +1322,26 @@ class Reticulum:
|
||||
else:
|
||||
return str(RNS.Transport.next_hop_interface(destination))
|
||||
|
||||
def get_first_hop_timeout(self, destination):
|
||||
if self.is_connected_to_shared_instance:
|
||||
try:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "first_hop_timeout", "destination_hash": destination})
|
||||
response = rpc_connection.recv()
|
||||
|
||||
if self.is_connected_to_shared_instance and hasattr(self, "_force_shared_instance_bitrate") and self._force_shared_instance_bitrate:
|
||||
simulated_latency = ((1/self._force_shared_instance_bitrate)*8)*RNS.Reticulum.MTU
|
||||
RNS.log("Adding simulated latency of "+RNS.prettytime(simulated_latency)+" to first hop timeout", RNS.LOG_DEBUG)
|
||||
response += simulated_latency
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while getting first hop timeout from shared instance: "+str(e), RNS.LOG_ERROR)
|
||||
return RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
|
||||
else:
|
||||
return RNS.Transport.first_hop_timeout(destination)
|
||||
|
||||
def get_next_hop(self, destination):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
@@ -1240,6 +1381,21 @@ class Reticulum:
|
||||
return None
|
||||
|
||||
|
||||
def get_packet_q(self, packet_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "packet_q", "packet_hash": packet_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else:
|
||||
for entry in RNS.Transport.local_client_q_cache:
|
||||
if entry[0] == packet_hash:
|
||||
return entry[1]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def should_use_implicit_proof():
|
||||
"""
|
||||
@@ -1263,6 +1419,10 @@ class Reticulum:
|
||||
"""
|
||||
return Reticulum.__transport_enabled
|
||||
|
||||
@staticmethod
|
||||
def probe_destination_enabled():
|
||||
return Reticulum.__allow_probes
|
||||
|
||||
# Default configuration file:
|
||||
__default_rns_config__ = '''# This is the default Reticulum config file.
|
||||
# You should probably edit it to include any additional,
|
||||
|
||||
+332
-107
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -66,7 +66,11 @@ class Transport:
|
||||
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
|
||||
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RW = 2 # Path request random window
|
||||
PATH_REQUEST_MI = 5 # Minimum interval in seconds for automated path requests
|
||||
PATH_REQUEST_MI = 20 # Minimum interval in seconds for automated path requests
|
||||
|
||||
STATE_UNKNOWN = 0x00
|
||||
STATE_UNRESPONSIVE = 0x01
|
||||
STATE_RESPONSIVE = 0x02
|
||||
|
||||
LINK_TIMEOUT = RNS.Link.STALE_TIME * 1.25
|
||||
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after 30 minutes
|
||||
@@ -94,6 +98,7 @@ class Transport:
|
||||
tunnels = {} # A table storing tunnels to other transport instances
|
||||
announce_rate_table = {} # A table for keeping track of announce rates
|
||||
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
|
||||
@@ -111,13 +116,15 @@ class Transport:
|
||||
|
||||
local_client_rssi_cache = []
|
||||
local_client_snr_cache = []
|
||||
local_client_q_cache = []
|
||||
LOCAL_CLIENT_CACHE_MAXSIZE = 512
|
||||
|
||||
pending_local_path_requests = {}
|
||||
|
||||
jobs_locked = False
|
||||
jobs_running = False
|
||||
job_interval = 0.250
|
||||
start_time = None
|
||||
jobs_locked = False
|
||||
jobs_running = False
|
||||
job_interval = 0.250
|
||||
links_last_checked = 0.0
|
||||
links_check_interval = 1.0
|
||||
receipts_last_checked = 0.0
|
||||
@@ -127,6 +134,8 @@ class Transport:
|
||||
hashlist_maxsize = 1000000
|
||||
tables_last_culled = 0.0
|
||||
tables_cull_interval = 5.0
|
||||
interface_last_jobs = 0.0
|
||||
interface_jobs_interval = 5.0
|
||||
|
||||
identity = None
|
||||
|
||||
@@ -269,7 +278,17 @@ class Transport:
|
||||
except Exception as e:
|
||||
RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.probe_destination_enabled():
|
||||
Transport.probe_destination = RNS.Destination(Transport.identity, RNS.Destination.IN, RNS.Destination.SINGLE, Transport.APP_NAME, "probe")
|
||||
Transport.probe_destination.accepts_links(False)
|
||||
Transport.probe_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
||||
Transport.probe_destination.announce()
|
||||
RNS.log("Transport Instance will respond to probe requests on "+str(Transport.probe_destination), RNS.LOG_NOTICE)
|
||||
else:
|
||||
Transport.probe_destination = None
|
||||
|
||||
RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE)
|
||||
Transport.start_time = time.time()
|
||||
|
||||
# Synthesize tunnels for any interfaces wanting it
|
||||
for interface in Transport.interfaces:
|
||||
@@ -286,7 +305,8 @@ class Transport:
|
||||
@staticmethod
|
||||
def jobs():
|
||||
outgoing = []
|
||||
path_requests = []
|
||||
path_requests = {}
|
||||
blocked_if = None
|
||||
Transport.jobs_running = True
|
||||
|
||||
try:
|
||||
@@ -314,7 +334,8 @@ class Transport:
|
||||
if time.time() - last_path_request > Transport.PATH_REQUEST_MI:
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link.destination.hash)+" since an attempted link was never established", RNS.LOG_DEBUG)
|
||||
if not link.destination.hash in path_requests:
|
||||
path_requests.append(link.destination.hash)
|
||||
blocked_if = None
|
||||
path_requests[link.destination.hash] = blocked_if
|
||||
|
||||
Transport.pending_links.remove(link)
|
||||
|
||||
@@ -334,18 +355,19 @@ class Transport:
|
||||
for receipt in Transport.receipts:
|
||||
receipt.check_timeout()
|
||||
if receipt.status != RNS.PacketReceipt.SENT:
|
||||
Transport.receipts.remove(receipt)
|
||||
if receipt in Transport.receipts:
|
||||
Transport.receipts.remove(receipt)
|
||||
|
||||
Transport.receipts_last_checked = time.time()
|
||||
|
||||
# Process announces needing retransmission
|
||||
if time.time() > Transport.announces_last_checked+Transport.announces_check_interval:
|
||||
completed_announces = []
|
||||
for destination_hash in Transport.announce_table:
|
||||
announce_entry = Transport.announce_table[destination_hash]
|
||||
if announce_entry[2] > Transport.PATHFINDER_R:
|
||||
RNS.log("Completed announce processing for "+RNS.prettyhexrep(destination_hash)+", retry limit reached", RNS.LOG_EXTREME)
|
||||
Transport.announce_table.pop(destination_hash)
|
||||
break
|
||||
completed_announces.append(destination_hash)
|
||||
else:
|
||||
if time.time() > announce_entry[1]:
|
||||
announce_entry[1] = time.time() + Transport.PATHFINDER_G + Transport.PATHFINDER_RW
|
||||
@@ -392,6 +414,10 @@ class Transport:
|
||||
Transport.announce_table[destination_hash] = held_entry
|
||||
RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG)
|
||||
|
||||
for destination_hash in completed_announces:
|
||||
if destination_hash in Transport.announce_table:
|
||||
Transport.announce_table.pop(destination_hash)
|
||||
|
||||
Transport.announces_last_checked = time.time()
|
||||
|
||||
|
||||
@@ -404,6 +430,12 @@ class Transport:
|
||||
Transport.discovery_pr_tags = Transport.discovery_pr_tags[len(Transport.discovery_pr_tags)-Transport.max_pr_tags:len(Transport.discovery_pr_tags)-1]
|
||||
|
||||
if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval:
|
||||
# Remove unneeded path state entries
|
||||
stale_path_states = []
|
||||
for destination_hash in Transport.path_states:
|
||||
if not destination_hash in Transport.destination_table:
|
||||
stale_path_states.append(destination_hash)
|
||||
|
||||
# Cull the reverse table according to timeout
|
||||
stale_reverse_entries = []
|
||||
for truncated_packet_hash in Transport.reverse_table:
|
||||
@@ -427,14 +459,49 @@ class Transport:
|
||||
if link_entry[6] in Transport.path_requests:
|
||||
last_path_request = Transport.path_requests[link_entry[6]]
|
||||
|
||||
lr_taken_hops = link_entry[5]
|
||||
|
||||
path_request_throttle = time.time() - last_path_request < Transport.PATH_REQUEST_MI
|
||||
path_request_conditions = False
|
||||
|
||||
# If the path has been invalidated between the time of
|
||||
# making the link request and now, try to rediscover it
|
||||
if not Transport.has_path(link_entry[6]):
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted link was never established, and path is now missing", RNS.LOG_DEBUG)
|
||||
path_request_conditions =True
|
||||
|
||||
# If this link request was originated from a local client
|
||||
# attempt to rediscover a path to the destination, if this
|
||||
# has not already happened recently.
|
||||
lr_taken_hops = link_entry[5]
|
||||
if lr_taken_hops == 0 and time.time() - last_path_request > Transport.PATH_REQUEST_MI:
|
||||
elif not path_request_throttle and lr_taken_hops == 0:
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted local client link was never established", RNS.LOG_DEBUG)
|
||||
path_request_conditions = True
|
||||
|
||||
# If the link destination was previously only 1 hop
|
||||
# away, this likely means that it was local to one
|
||||
# of our interfaces, and that it roamed somewhere else.
|
||||
# In that case, try to discover a new path.
|
||||
elif not path_request_throttle and Transport.hops_to(link_entry[6]) == 1:
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted link was never established, and destination was previously local to an interface on this instance", RNS.LOG_DEBUG)
|
||||
path_request_conditions = True
|
||||
blocked_if = link_entry[4]
|
||||
|
||||
# If the link initiator is only 1 hop away,
|
||||
# this likely means that network topology has
|
||||
# changed. In that case, we try to discover a new path,
|
||||
# and mark the old one as potentially unresponsive.
|
||||
elif not path_request_throttle and lr_taken_hops == 1:
|
||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted link was never established, and link initiator is local to an interface on this instance", RNS.LOG_DEBUG)
|
||||
path_request_conditions = True
|
||||
blocked_if = link_entry[4]
|
||||
|
||||
if RNS.Reticulum.transport_enabled():
|
||||
if hasattr(link_entry[4], "mode") and link_entry[4].mode != RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
||||
Transport.mark_path_unresponsive(link_entry[6])
|
||||
|
||||
if path_request_conditions:
|
||||
if not link_entry[6] in path_requests:
|
||||
path_requests.append(link_entry[6])
|
||||
path_requests[link_entry[6]] = blocked_if
|
||||
|
||||
if not RNS.Reticulum.transport_enabled():
|
||||
# Drop current path if we are not a transport instance, to
|
||||
@@ -502,8 +569,6 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Removed "+str(ti)+" tunnel paths", RNS.LOG_EXTREME)
|
||||
|
||||
|
||||
|
||||
i = 0
|
||||
for truncated_packet_hash in stale_reverse_entries:
|
||||
Transport.reverse_table.pop(truncated_packet_hash)
|
||||
@@ -515,8 +580,6 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Released "+str(i)+" reverse table entries", RNS.LOG_EXTREME)
|
||||
|
||||
|
||||
|
||||
i = 0
|
||||
for link_id in stale_links:
|
||||
Transport.link_table.pop(link_id)
|
||||
@@ -561,8 +624,24 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Removed "+str(i)+" tunnels", RNS.LOG_EXTREME)
|
||||
|
||||
i = 0
|
||||
for destination_hash in stale_path_states:
|
||||
Transport.path_states.pop(destination_hash)
|
||||
i += 1
|
||||
|
||||
if i > 0:
|
||||
if i == 1:
|
||||
RNS.log("Removed "+str(i)+" path state entry", RNS.LOG_EXTREME)
|
||||
else:
|
||||
RNS.log("Removed "+str(i)+" path state entries", RNS.LOG_EXTREME)
|
||||
|
||||
Transport.tables_last_culled = time.time()
|
||||
|
||||
if time.time() > Transport.interface_last_jobs + Transport.interface_jobs_interval:
|
||||
for interface in Transport.interfaces:
|
||||
interface.process_held_announces()
|
||||
Transport.interface_last_jobs = time.time()
|
||||
|
||||
else:
|
||||
# Transport jobs were locked, do nothing
|
||||
pass
|
||||
@@ -577,7 +656,17 @@ class Transport:
|
||||
packet.send()
|
||||
|
||||
for destination_hash in path_requests:
|
||||
Transport.request_path(destination_hash)
|
||||
blocked_if = path_requests[destination_hash]
|
||||
if blocked_if == None:
|
||||
Transport.request_path(destination_hash)
|
||||
else:
|
||||
for interface in Transport.interfaces:
|
||||
if interface != blocked_if:
|
||||
# RNS.log("Transmitting path request on "+str(interface), RNS.LOG_DEBUG)
|
||||
Transport.request_path(destination_hash, on_interface=interface)
|
||||
else:
|
||||
pass
|
||||
# RNS.log("Blocking path request on "+str(interface), RNS.LOG_DEBUG)
|
||||
|
||||
@staticmethod
|
||||
def transmit(interface, raw):
|
||||
@@ -634,6 +723,29 @@ class Transport:
|
||||
sent = False
|
||||
outbound_time = time.time()
|
||||
|
||||
generate_receipt = False
|
||||
if (packet.create_receipt == True and
|
||||
# Only generate receipts for DATA packets
|
||||
packet.packet_type == RNS.Packet.DATA and
|
||||
# Don't generate receipts for PLAIN destinations
|
||||
packet.destination.type != RNS.Destination.PLAIN and
|
||||
# Don't generate receipts for link-related packets
|
||||
not (packet.context >= RNS.Packet.KEEPALIVE and packet.context <= RNS.Packet.LRPROOF) and
|
||||
# Don't generate receipts for resource packets
|
||||
not (packet.context >= RNS.Packet.RESOURCE and packet.context <= RNS.Packet.RESOURCE_RCL)):
|
||||
|
||||
generate_receipt = True
|
||||
|
||||
def packet_sent(packet):
|
||||
packet.sent = True
|
||||
packet.sent_at = time.time()
|
||||
|
||||
if generate_receipt:
|
||||
packet.receipt = RNS.PacketReceipt(packet)
|
||||
Transport.receipts.append(packet.receipt)
|
||||
|
||||
Transport.cache(packet)
|
||||
|
||||
# Check if we have a known path for the destination in the path table
|
||||
if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination.type != RNS.Destination.PLAIN and packet.destination.type != RNS.Destination.GROUP and packet.destination_hash in Transport.destination_table:
|
||||
outbound_interface = Transport.destination_table[packet.destination_hash][5]
|
||||
@@ -651,6 +763,7 @@ class Transport:
|
||||
new_raw += packet.raw[1:2]
|
||||
new_raw += Transport.destination_table[packet.destination_hash][1]
|
||||
new_raw += packet.raw[2:]
|
||||
packet_sent(packet)
|
||||
Transport.transmit(outbound_interface, new_raw)
|
||||
Transport.destination_table[packet.destination_hash][0] = time.time()
|
||||
sent = True
|
||||
@@ -670,6 +783,7 @@ class Transport:
|
||||
new_raw += packet.raw[1:2]
|
||||
new_raw += Transport.destination_table[packet.destination_hash][1]
|
||||
new_raw += packet.raw[2:]
|
||||
packet_sent(packet)
|
||||
Transport.transmit(outbound_interface, new_raw)
|
||||
Transport.destination_table[packet.destination_hash][0] = time.time()
|
||||
sent = True
|
||||
@@ -678,6 +792,7 @@ class Transport:
|
||||
# directly reachable, and also on which interface, so we
|
||||
# simply transmit the packet directly on that one.
|
||||
else:
|
||||
packet_sent(packet)
|
||||
Transport.transmit(outbound_interface, packet.raw)
|
||||
sent = True
|
||||
|
||||
@@ -842,28 +957,11 @@ class Transport:
|
||||
# thread.start()
|
||||
|
||||
Transport.transmit(interface, packet.raw)
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
interface.sent_announce()
|
||||
packet_sent(packet)
|
||||
sent = True
|
||||
|
||||
if sent:
|
||||
packet.sent = True
|
||||
packet.sent_at = time.time()
|
||||
|
||||
# Don't generate receipt if it has been explicitly disabled
|
||||
if (packet.create_receipt == True and
|
||||
# Only generate receipts for DATA packets
|
||||
packet.packet_type == RNS.Packet.DATA and
|
||||
# Don't generate receipts for PLAIN destinations
|
||||
packet.destination.type != RNS.Destination.PLAIN and
|
||||
# Don't generate receipts for link-related packets
|
||||
not (packet.context >= RNS.Packet.KEEPALIVE and packet.context <= RNS.Packet.LRPROOF) and
|
||||
# Don't generate receipts for resource packets
|
||||
not (packet.context >= RNS.Packet.RESOURCE and packet.context <= RNS.Packet.RESOURCE_RCL)):
|
||||
|
||||
packet.receipt = RNS.PacketReceipt(packet)
|
||||
Transport.receipts.append(packet.receipt)
|
||||
|
||||
Transport.cache(packet)
|
||||
|
||||
Transport.jobs_locked = False
|
||||
return sent
|
||||
|
||||
@@ -882,6 +980,8 @@ class Transport:
|
||||
return True
|
||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||
return True
|
||||
if packet.context == RNS.Packet.CHANNEL:
|
||||
return True
|
||||
|
||||
if packet.destination_type == RNS.Destination.PLAIN:
|
||||
if packet.packet_type != RNS.Packet.ANNOUNCE:
|
||||
@@ -993,6 +1093,7 @@ class Transport:
|
||||
|
||||
packet = RNS.Packet(None, raw)
|
||||
if not packet.unpack():
|
||||
Transport.jobs_locked = False
|
||||
return
|
||||
|
||||
packet.receiving_interface = interface
|
||||
@@ -1006,7 +1107,7 @@ class Transport:
|
||||
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()
|
||||
Transport.local_client_rssi_cache.pop(0)
|
||||
|
||||
if hasattr(interface, "r_stat_snr"):
|
||||
if interface.r_stat_rssi != None:
|
||||
@@ -1015,7 +1116,16 @@ class Transport:
|
||||
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()
|
||||
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)
|
||||
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
if Transport.is_local_client_interface(interface):
|
||||
@@ -1074,6 +1184,7 @@ class Transport:
|
||||
# normal processing.
|
||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||
if Transport.cache_request_packet(packet):
|
||||
Transport.jobs_locked = False
|
||||
return
|
||||
|
||||
# If the packet is in transport, check whether we
|
||||
@@ -1107,7 +1218,8 @@ class Transport:
|
||||
|
||||
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
now = time.time()
|
||||
proof_timeout = now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops)
|
||||
proof_timeout = Transport.extra_link_proof_timeout(packet.receiving_interface)
|
||||
proof_timeout += now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops)
|
||||
|
||||
# Entry format is
|
||||
link_entry = [ now, # 0: Timestamp,
|
||||
@@ -1165,7 +1277,7 @@ class Transport:
|
||||
# Also check that expected hop count matches
|
||||
if packet.hops == link_entry[5]:
|
||||
outbound_interface = link_entry[2]
|
||||
|
||||
|
||||
if outbound_interface != None:
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
@@ -1180,8 +1292,21 @@ class Transport:
|
||||
# announces, queueing rebroadcasts of these, and removal
|
||||
# of queued announce rebroadcasts once handed to the next node.
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
if interface != None and RNS.Identity.validate_announce(packet, only_validate_signature=True):
|
||||
interface.received_announce()
|
||||
|
||||
if not packet.destination_hash in Transport.destination_table:
|
||||
# This is an unknown destination, and we'll apply
|
||||
# potential ingress limiting. Already known
|
||||
# destinations will have re-announces controlled
|
||||
# by normal announce rate limiting.
|
||||
if interface.should_ingress_limit():
|
||||
interface.hold_announce(packet)
|
||||
Transport.jobs_locked = False
|
||||
return
|
||||
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
||||
if local_destination == None and RNS.Identity.validate_announce(packet):
|
||||
if local_destination == None and RNS.Identity.validate_announce(packet):
|
||||
if packet.transport_id != None:
|
||||
received_from = packet.transport_id
|
||||
|
||||
@@ -1231,6 +1356,7 @@ class Transport:
|
||||
# TODO: Check whether this approach works
|
||||
# under all circumstances
|
||||
if not random_blob in random_blobs:
|
||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
||||
should_add = True
|
||||
else:
|
||||
should_add = False
|
||||
@@ -1248,21 +1374,39 @@ class Transport:
|
||||
if path_announce_emitted >= announce_emitted:
|
||||
break
|
||||
|
||||
# If the path has expired, consider this
|
||||
# announce for adding to the path table.
|
||||
if (now >= path_expires):
|
||||
# We also check that the announce is
|
||||
# We check that the announce is
|
||||
# different from ones we've already heard,
|
||||
# to avoid loops in the network
|
||||
if not random_blob in random_blobs:
|
||||
# TODO: Check that this ^ approach actually
|
||||
# works under all circumstances
|
||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce due to expired path", RNS.LOG_DEBUG)
|
||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
||||
should_add = True
|
||||
else:
|
||||
should_add = False
|
||||
else:
|
||||
# If the path is not expired, but the emission
|
||||
# is more recent, and we haven't already heard
|
||||
# this announce before, update the path table.
|
||||
if (announce_emitted > path_announce_emitted):
|
||||
if not random_blob in random_blobs:
|
||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since it was more recently emitted", RNS.LOG_DEBUG)
|
||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
||||
should_add = True
|
||||
else:
|
||||
should_add = False
|
||||
|
||||
# If we have already heard this announce before,
|
||||
# but the path has been marked as unresponsive
|
||||
# by a failed communications attempt or similar,
|
||||
# allow updating the path table to this one.
|
||||
elif announce_emitted == path_announce_emitted:
|
||||
if Transport.path_is_unresponsive(packet.destination_hash):
|
||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since previously tried path was unresponsive", RNS.LOG_DEBUG)
|
||||
should_add = True
|
||||
else:
|
||||
should_add = False
|
||||
@@ -1273,7 +1417,7 @@ class Transport:
|
||||
should_add = True
|
||||
|
||||
if should_add:
|
||||
now = time.time()
|
||||
now = time.time()
|
||||
|
||||
rate_blocked = False
|
||||
if packet.context != RNS.Packet.PATH_RESPONSE and packet.receiving_interface.announce_rate_target != None:
|
||||
@@ -1491,7 +1635,7 @@ class Transport:
|
||||
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
# Handling for linkrequests to local destinations
|
||||
# Handling for link requests to local destinations
|
||||
elif packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
|
||||
for destination in Transport.destinations:
|
||||
@@ -1530,32 +1674,35 @@ class Transport:
|
||||
# needs to be transported
|
||||
if (RNS.Reticulum.transport_enabled() or for_local_client_link or from_local_client) and packet.destination_hash in Transport.link_table:
|
||||
link_entry = Transport.link_table[packet.destination_hash]
|
||||
if packet.receiving_interface == link_entry[2]:
|
||||
try:
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2:
|
||||
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]
|
||||
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:
|
||||
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
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if peer_identity.validate(signature, signed_data):
|
||||
RNS.log("Link request proof validated for transport via "+str(link_entry[4]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[2:]
|
||||
Transport.link_table[packet.destination_hash][7] = True
|
||||
Transport.transmit(link_entry[4], new_raw)
|
||||
if peer_identity.validate(signature, signed_data):
|
||||
RNS.log("Link request proof validated for transport via "+str(link_entry[4]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[2:]
|
||||
Transport.link_table[packet.destination_hash][7] = True
|
||||
Transport.transmit(link_entry[4], new_raw)
|
||||
|
||||
else:
|
||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while transporting link request proof. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while transporting link request proof. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
else:
|
||||
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG)
|
||||
RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG)
|
||||
else:
|
||||
# Check if we can deliver it to a local
|
||||
# pending link
|
||||
@@ -1582,7 +1729,7 @@ class Transport:
|
||||
if (RNS.Reticulum.transport_enabled() or from_local_client or proof_for_local_client) and packet.destination_hash in Transport.reverse_table:
|
||||
reverse_entry = Transport.reverse_table.pop(packet.destination_hash)
|
||||
if packet.receiving_interface == reverse_entry[1]:
|
||||
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_DEBUG)
|
||||
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[2:]
|
||||
@@ -1597,9 +1744,6 @@ class Transport:
|
||||
if receipt.hash == proof_hash:
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
else:
|
||||
# TODO: This looks like it should actually
|
||||
# be rewritten when implicit proofs are added.
|
||||
|
||||
# In case of an implicit proof, we have
|
||||
# to check every single outstanding receipt
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
@@ -1738,6 +1882,8 @@ class Transport:
|
||||
def activate_link(link):
|
||||
RNS.log("Activating link "+str(link), RNS.LOG_EXTREME)
|
||||
if link in Transport.pending_links:
|
||||
if link.status != RNS.Link.ACTIVE:
|
||||
raise IOError("Invalid link state for link activation: "+str(link.status))
|
||||
Transport.pending_links.remove(link)
|
||||
Transport.active_links.append(link)
|
||||
link.status = RNS.Link.ACTIVE
|
||||
@@ -1775,8 +1921,11 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def should_cache(packet):
|
||||
if packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
return True
|
||||
# TODO: Rework the caching system. It's currently
|
||||
# not very useful to even cache Resource proofs,
|
||||
# disabling it for now, until redesigned.
|
||||
# if packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
# return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -1799,8 +1948,7 @@ class Transport:
|
||||
file.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error writing packet to cache", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e))
|
||||
RNS.log("Error writing packet to cache. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def get_cached_packet(packet_hash):
|
||||
@@ -1900,6 +2048,45 @@ class Transport:
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_interface_bitrate(destination_hash):
|
||||
next_hop_interface = Transport.next_hop_interface(destination_hash)
|
||||
if next_hop_interface != None:
|
||||
return next_hop_interface.bitrate
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_per_bit_latency(destination_hash):
|
||||
next_hop_interface_bitrate = Transport.next_hop_interface_bitrate(destination_hash)
|
||||
if next_hop_interface_bitrate != None:
|
||||
return (1/next_hop_interface_bitrate)
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_per_byte_latency(destination_hash):
|
||||
per_bit_latency = Transport.next_hop_per_bit_latency(destination_hash)
|
||||
if per_bit_latency != None:
|
||||
return per_bit_latency*8
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def first_hop_timeout(destination_hash):
|
||||
latency = Transport.next_hop_per_byte_latency(destination_hash)
|
||||
if latency != None:
|
||||
return RNS.Reticulum.MTU * latency + RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
else:
|
||||
return RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
|
||||
@staticmethod
|
||||
def extra_link_proof_timeout(interface):
|
||||
if interface != None:
|
||||
return ((1/interface.bitrate)*8)*RNS.Reticulum.MTU
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def expire_path(destination_hash):
|
||||
if destination_hash in Transport.destination_table:
|
||||
@@ -1909,6 +2096,37 @@ class Transport:
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def mark_path_unresponsive(destination_hash):
|
||||
if destination_hash in Transport.destination_table:
|
||||
Transport.path_states[destination_hash] = Transport.STATE_UNRESPONSIVE
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def mark_path_responsive(destination_hash):
|
||||
if destination_hash in Transport.destination_table:
|
||||
Transport.path_states[destination_hash] = Transport.STATE_RESPONSIVE
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def mark_path_unknown_state(destination_hash):
|
||||
if destination_hash in Transport.destination_table:
|
||||
Transport.path_states[destination_hash] = Transport.STATE_UNKNOWN
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def path_is_unresponsive(destination_hash):
|
||||
if destination_hash in Transport.path_states:
|
||||
if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def request_path(destination_hash, on_interface=None, tag=None, recursive=False):
|
||||
"""
|
||||
@@ -2041,41 +2259,45 @@ class Transport:
|
||||
packet = Transport.destination_table[destination_hash][6]
|
||||
next_hop = Transport.destination_table[destination_hash][1]
|
||||
received_from = Transport.destination_table[destination_hash][5]
|
||||
|
||||
if requestor_transport_id != None and next_hop == requestor_transport_id:
|
||||
# TODO: Find a bandwidth efficient way to invalidate our
|
||||
# known path on this signal. The obvious way of signing
|
||||
# path requests with transport instance keys is quite
|
||||
# inefficient. There is probably a better way. Doing
|
||||
# path invalidation here would decrease the network
|
||||
# convergence time. Maybe just drop it?
|
||||
RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_DEBUG)
|
||||
|
||||
if attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from:
|
||||
RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", RNS.LOG_DEBUG)
|
||||
|
||||
now = time.time()
|
||||
retries = Transport.PATHFINDER_R
|
||||
local_rebroadcasts = 0
|
||||
block_rebroadcasts = True
|
||||
announce_hops = packet.hops
|
||||
|
||||
if is_from_local_client:
|
||||
retransmit_timeout = now
|
||||
if requestor_transport_id != None and next_hop == requestor_transport_id:
|
||||
# TODO: Find a bandwidth efficient way to invalidate our
|
||||
# known path on this signal. The obvious way of signing
|
||||
# path requests with transport instance keys is quite
|
||||
# inefficient. There is probably a better way. Doing
|
||||
# path invalidation here would decrease the network
|
||||
# convergence time. Maybe just drop it?
|
||||
RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_DEBUG)
|
||||
else:
|
||||
# TODO: Look at this timing
|
||||
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE # + (RNS.rand() * Transport.PATHFINDER_RW)
|
||||
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", RNS.LOG_DEBUG)
|
||||
|
||||
# This handles an edge case where a peer sends a past
|
||||
# request for a destination just after an announce for
|
||||
# said destination has arrived, but before it has been
|
||||
# rebroadcast locally. In such a case the actual announce
|
||||
# is temporarily held, and then reinserted when the path
|
||||
# request has been served to the peer.
|
||||
if packet.destination_hash in Transport.announce_table:
|
||||
held_entry = Transport.announce_table[packet.destination_hash]
|
||||
Transport.held_announces[packet.destination_hash] = held_entry
|
||||
|
||||
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, announce_hops, packet, local_rebroadcasts, block_rebroadcasts, attached_interface]
|
||||
now = time.time()
|
||||
retries = Transport.PATHFINDER_R
|
||||
local_rebroadcasts = 0
|
||||
block_rebroadcasts = True
|
||||
announce_hops = packet.hops
|
||||
|
||||
if is_from_local_client:
|
||||
retransmit_timeout = now
|
||||
else:
|
||||
# TODO: Look at this timing
|
||||
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE # + (RNS.rand() * Transport.PATHFINDER_RW)
|
||||
|
||||
# This handles an edge case where a peer sends a past
|
||||
# request for a destination just after an announce for
|
||||
# said destination has arrived, but before it has been
|
||||
# rebroadcast locally. In such a case the actual announce
|
||||
# is temporarily held, and then reinserted when the path
|
||||
# request has been served to the peer.
|
||||
if packet.destination_hash in Transport.announce_table:
|
||||
held_entry = Transport.announce_table[packet.destination_hash]
|
||||
Transport.held_announces[packet.destination_hash] = held_entry
|
||||
|
||||
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, announce_hops, packet, local_rebroadcasts, block_rebroadcasts, attached_interface]
|
||||
|
||||
elif is_from_local_client:
|
||||
# Forward path request on all interfaces
|
||||
@@ -2159,7 +2381,10 @@ class Transport:
|
||||
pass
|
||||
|
||||
for interface in detachable_interfaces:
|
||||
interface.detach()
|
||||
try:
|
||||
interface.detach()
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while detaching "+str(interface)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def shared_connection_disappeared():
|
||||
|
||||
+400
-45
@@ -24,6 +24,7 @@
|
||||
|
||||
import RNS
|
||||
import argparse
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
@@ -34,9 +35,12 @@ APP_NAME = "rncp"
|
||||
allow_all = False
|
||||
allowed_identity_hashes = []
|
||||
|
||||
def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None, disable_announce = False):
|
||||
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None, announce = False):
|
||||
global allow_all, allowed_identity_hashes
|
||||
from tempfile import TemporaryFile
|
||||
identity = None
|
||||
if announce < 0:
|
||||
announce = False
|
||||
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
|
||||
@@ -54,16 +58,48 @@ def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_ident
|
||||
|
||||
if display_identity:
|
||||
print("Identity : "+str(identity))
|
||||
print("Receiving on : "+RNS.prettyhexrep(destination.hash))
|
||||
print("Listening on : "+RNS.prettyhexrep(destination.hash))
|
||||
exit(0)
|
||||
|
||||
if disable_auth:
|
||||
allow_all = True
|
||||
else:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
try:
|
||||
allowed_file_name = "allowed_identities"
|
||||
allowed_file = None
|
||||
if os.path.isfile(os.path.expanduser("/etc/rncp/"+allowed_file_name)):
|
||||
allowed_file = os.path.expanduser("/etc/rncp/"+allowed_file_name)
|
||||
elif os.path.isfile(os.path.expanduser("~/.config/rncp/"+allowed_file_name)):
|
||||
allowed_file = os.path.expanduser("~/.config/rncp/"+allowed_file_name)
|
||||
elif os.path.isfile(os.path.expanduser("~/.rncp/"+allowed_file_name)):
|
||||
allowed_file = os.path.expanduser("~/.rncp/"+allowed_file_name)
|
||||
if allowed_file != None:
|
||||
af = open(allowed_file, "r")
|
||||
al = af.read().replace("\r", "").split("\n")
|
||||
ali = []
|
||||
for a in al:
|
||||
if len(a) == dest_len:
|
||||
ali.append(a)
|
||||
|
||||
if len(ali) > 0:
|
||||
if not allowed:
|
||||
allowed = ali
|
||||
else:
|
||||
allowed.extend(ali)
|
||||
if len(ali) == 1:
|
||||
ms = "y"
|
||||
else:
|
||||
ms = "ies"
|
||||
|
||||
RNS.log("Loaded "+str(len(ali))+" allowed identit"+ms+" from "+str(allowed_file), RNS.LOG_VERBOSE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while parsing allowed_identities file. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
if allowed != None:
|
||||
for a in allowed:
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(a) != dest_len:
|
||||
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
|
||||
try:
|
||||
@@ -78,16 +114,60 @@ def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_ident
|
||||
if len(allowed_identity_hashes) < 1 and not disable_auth:
|
||||
print("Warning: No allowed identities configured, rncp will not accept any files!")
|
||||
|
||||
destination.set_link_established_callback(receive_link_established)
|
||||
print("rncp ready to receive on "+RNS.prettyhexrep(destination.hash))
|
||||
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
|
||||
target_link = None
|
||||
for link in RNS.Transport.active_links:
|
||||
if link.link_id == link_id:
|
||||
target_link = link
|
||||
|
||||
if not disable_announce:
|
||||
destination.announce()
|
||||
file_path = os.path.expanduser(data)
|
||||
if not os.path.isfile(file_path):
|
||||
RNS.log("Client-requested file not found: "+str(file_path), RNS.LOG_VERBOSE)
|
||||
return False
|
||||
else:
|
||||
if target_link != None:
|
||||
RNS.log("Sending file "+str(file_path)+" to client", RNS.LOG_VERBOSE)
|
||||
|
||||
temp_file = TemporaryFile()
|
||||
real_file = open(file_path, "rb")
|
||||
filename_bytes = os.path.basename(file_path).encode("utf-8")
|
||||
filename_len = len(filename_bytes)
|
||||
|
||||
if filename_len > 0xFFFF:
|
||||
print("Filename exceeds max size, cannot send")
|
||||
exit(1)
|
||||
else:
|
||||
print("Preparing file...", end=" ")
|
||||
|
||||
temp_file.write(filename_len.to_bytes(2, "big"))
|
||||
temp_file.write(filename_bytes)
|
||||
temp_file.write(real_file.read())
|
||||
temp_file.seek(0)
|
||||
|
||||
fetch_resource = RNS.Resource(temp_file, target_link)
|
||||
return True
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
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)
|
||||
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
|
||||
|
||||
if announce >= 0:
|
||||
def job():
|
||||
destination.announce()
|
||||
if announce > 0:
|
||||
while True:
|
||||
time.sleep(announce)
|
||||
destination.announce()
|
||||
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
def receive_link_established(link):
|
||||
def client_link_established(link):
|
||||
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
|
||||
link.set_remote_identified_callback(receive_sender_identified)
|
||||
link.set_resource_strategy(RNS.Link.ACCEPT_APP)
|
||||
@@ -181,7 +261,221 @@ def sender_progress(resource):
|
||||
resource_done = True
|
||||
|
||||
link = None
|
||||
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
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
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(destination) != dest_len:
|
||||
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
|
||||
try:
|
||||
destination_hash = bytes.fromhex(destination)
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid destination entered. Check your input.")
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit(1)
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
|
||||
|
||||
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
|
||||
if os.path.isfile(identity_path):
|
||||
identity = RNS.Identity.from_file(identity_path)
|
||||
if identity == None:
|
||||
RNS.log("Could not load identity for rncp. The identity file at \""+str(identity_path)+"\" may be corrupt or unreadable.", RNS.LOG_ERROR)
|
||||
exit(2)
|
||||
else:
|
||||
identity = None
|
||||
|
||||
if identity == None:
|
||||
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
|
||||
identity = RNS.Identity()
|
||||
identity.to_file(identity_path)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
if silent:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
|
||||
else:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
estab_timeout = time.time()+timeout
|
||||
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
if silent:
|
||||
print("Path not found")
|
||||
else:
|
||||
print("\r \rPath 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=" ")
|
||||
|
||||
listener_identity = RNS.Identity.recall(destination_hash)
|
||||
listener_destination = RNS.Destination(
|
||||
listener_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"receive"
|
||||
)
|
||||
|
||||
link = RNS.Link(listener_destination)
|
||||
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
if silent:
|
||||
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rCould 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=" ")
|
||||
|
||||
link.identify(identity)
|
||||
|
||||
request_resolved = False
|
||||
request_status = "unknown"
|
||||
resource_resolved = False
|
||||
resource_status = "unrequested"
|
||||
current_resource = None
|
||||
def request_response(request_receipt):
|
||||
nonlocal request_resolved, request_status
|
||||
if request_receipt.response == False:
|
||||
request_status = "not_found"
|
||||
elif request_receipt.response == None:
|
||||
request_status = "remote_error"
|
||||
else:
|
||||
request_status = "found"
|
||||
|
||||
request_resolved = True
|
||||
|
||||
def request_failed(request_receipt):
|
||||
nonlocal request_resolved, request_status
|
||||
request_status = "unknown"
|
||||
request_resolved = True
|
||||
|
||||
def fetch_resource_started(resource):
|
||||
nonlocal resource_status
|
||||
current_resource = resource
|
||||
current_resource.progress_callback(sender_progress)
|
||||
resource_status = "started"
|
||||
|
||||
def fetch_resource_concluded(resource):
|
||||
nonlocal resource_resolved, resource_status
|
||||
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):
|
||||
counter += 1
|
||||
saved_filename = filename+"."+str(counter)
|
||||
|
||||
file = open(saved_filename, "wb")
|
||||
file.write(resource.data.read())
|
||||
file.close()
|
||||
resource_status = "completed"
|
||||
|
||||
else:
|
||||
print("Invalid data received, ignoring resource")
|
||||
resource_status = "invalid_data"
|
||||
|
||||
else:
|
||||
print("Resource failed")
|
||||
resource_status = "failed"
|
||||
|
||||
resource_resolved = True
|
||||
|
||||
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
|
||||
link.set_resource_started_callback(fetch_resource_started)
|
||||
link.set_resource_concluded_callback(fetch_resource_concluded)
|
||||
link.request("fetch_file", data=file, response_callback=request_response, failed_callback=request_failed)
|
||||
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
while not request_resolved:
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if request_status == "not_found":
|
||||
if not silent: print("\r \r", end="")
|
||||
print("Fetch request failed, the file "+str(file)+" was not found on the remote")
|
||||
link.teardown()
|
||||
time.sleep(1)
|
||||
exit(0)
|
||||
elif request_status == "remote_error":
|
||||
if not silent: print("\r \r", end="")
|
||||
print("Fetch request failed due to an error on the remote system")
|
||||
link.teardown()
|
||||
time.sleep(1)
|
||||
exit(0)
|
||||
elif request_status == "unknown":
|
||||
if not silent: print("\r \r", end="")
|
||||
print("Fetch request failed due to an unknown error (probably not authorised)")
|
||||
link.teardown()
|
||||
time.sleep(1)
|
||||
exit(0)
|
||||
elif request_status == "found":
|
||||
if not silent: print("\r \r", end="")
|
||||
|
||||
while not resource_resolved:
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
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"
|
||||
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
|
||||
else:
|
||||
print("\r \rWaiting for transfer to start "+syms[i]+" ", end=" ")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if current_resource.status != RNS.Resource.COMPLETE:
|
||||
if silent:
|
||||
print("The transfer failed")
|
||||
else:
|
||||
print("\r \rThe transfer failed")
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print(str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \r"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
|
||||
link.teardown()
|
||||
time.sleep(0.25)
|
||||
exit(0)
|
||||
|
||||
link.teardown()
|
||||
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
|
||||
from tempfile import TemporaryFile
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
@@ -226,7 +520,12 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
|
||||
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
|
||||
if os.path.isfile(identity_path):
|
||||
identity = RNS.Identity.from_file(identity_path)
|
||||
identity = RNS.Identity.from_file(identity_path)
|
||||
if identity == None:
|
||||
RNS.log("Could not load identity for rncp. The identity file at \""+str(identity_path)+"\" may be corrupt or unreadable.", RNS.LOG_ERROR)
|
||||
exit(2)
|
||||
else:
|
||||
identity = None
|
||||
|
||||
if identity == None:
|
||||
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
|
||||
@@ -235,23 +534,33 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
if silent:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
|
||||
else:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
estab_timeout = time.time()+timeout
|
||||
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
print("\r \rPath not found")
|
||||
if silent:
|
||||
print("Path not found")
|
||||
else:
|
||||
print("\r \rPath not found")
|
||||
exit(1)
|
||||
else:
|
||||
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
|
||||
if silent:
|
||||
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
|
||||
|
||||
receiver_identity = RNS.Identity.recall(destination_hash)
|
||||
receiver_destination = RNS.Destination(
|
||||
@@ -264,48 +573,75 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
|
||||
link = RNS.Link(receiver_destination)
|
||||
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||
if time.time() > estab_timeout:
|
||||
if silent:
|
||||
print("Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
|
||||
else:
|
||||
print("\r \rLink 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))
|
||||
exit(1)
|
||||
else:
|
||||
print("\r \rAdvertising file resource ", end=" ")
|
||||
if silent:
|
||||
print("Advertising file resource...")
|
||||
else:
|
||||
print("\r \rAdvertising file resource ", end=" ")
|
||||
|
||||
link.identify(identity)
|
||||
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
|
||||
current_resource = resource
|
||||
|
||||
while resource.status < RNS.Resource.TRANSFERRING:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
if not silent:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
|
||||
if resource.status > RNS.Resource.COMPLETE:
|
||||
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
|
||||
if silent:
|
||||
print("File was not accepted by "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
|
||||
exit(1)
|
||||
else:
|
||||
print("\r \rTransferring file ", end=" ")
|
||||
if silent:
|
||||
print("Transferring file...")
|
||||
else:
|
||||
print("\r \rTransferring file ", end=" ")
|
||||
|
||||
while not resource_done:
|
||||
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"
|
||||
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
if not silent:
|
||||
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"
|
||||
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if current_resource.status != RNS.Resource.COMPLETE:
|
||||
print("\r \rThe transfer failed")
|
||||
if silent:
|
||||
print("The transfer failed")
|
||||
else:
|
||||
print("\r \rThe transfer failed")
|
||||
exit(1)
|
||||
else:
|
||||
print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||
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))
|
||||
link.teardown()
|
||||
time.sleep(0.25)
|
||||
real_file.close()
|
||||
@@ -320,19 +656,21 @@ def main():
|
||||
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
|
||||
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
|
||||
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
|
||||
parser.add_argument("-r", '--receive', action='store_true', default=False, help="wait for incoming files")
|
||||
parser.add_argument("-b", '--no-announce', action='store_true', default=False, help="don't announce at program start")
|
||||
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("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
|
||||
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="accept from this identity", type=str)
|
||||
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept files 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("--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__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.receive or args.print_identity:
|
||||
receive(
|
||||
if args.listen or args.print_identity:
|
||||
listen(
|
||||
configdir = args.config,
|
||||
verbosity=args.verbose,
|
||||
quietness=args.quiet,
|
||||
@@ -340,9 +678,25 @@ def main():
|
||||
display_identity=args.print_identity,
|
||||
# limit=args.limit,
|
||||
disable_auth=args.no_auth,
|
||||
disable_announce=args.no_announce,
|
||||
announce=args.b,
|
||||
)
|
||||
|
||||
elif args.fetch:
|
||||
if args.destination != None and args.file != None:
|
||||
fetch(
|
||||
configdir = args.config,
|
||||
verbosity = args.verbose,
|
||||
quietness = args.quiet,
|
||||
destination = args.destination,
|
||||
file = args.file,
|
||||
timeout = args.w,
|
||||
silent = args.silent,
|
||||
)
|
||||
else:
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
|
||||
elif args.destination != None and args.file != None:
|
||||
send(
|
||||
configdir = args.config,
|
||||
@@ -351,6 +705,7 @@ def main():
|
||||
destination = args.destination,
|
||||
file = args.file,
|
||||
timeout = args.w,
|
||||
silent = args.silent,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
@@ -69,7 +69,7 @@ def main():
|
||||
parser.add_argument("-q", "--quiet", action="count", default=0, help="decrease verbosity")
|
||||
|
||||
parser.add_argument("-a", "--announce", metavar="aspects", action="store", default=None, help="announce a destination based on this Identity")
|
||||
parser.add_argument("-H", "--hash", metavar="aspects", action="store", default=None, help="show destination hash5s for other aspects for this Identity")
|
||||
parser.add_argument("-H", "--hash", metavar="aspects", action="store", default=None, help="show destination hashes for other aspects for this Identity")
|
||||
parser.add_argument("-e", "--encrypt", metavar="path", action="store", default=None, help="encrypt file")
|
||||
parser.add_argument("-d", "--decrypt", metavar="path", action="store", default=None, help="decrypt file")
|
||||
parser.add_argument("-s", "--sign", metavar="path", action="store", default=None, help="sign file")
|
||||
@@ -88,7 +88,7 @@ def main():
|
||||
|
||||
parser.add_argument("-b", "--base64", action="store_true", default=False, help=argparse.SUPPRESS) # help="Use base64-encoded input and output")
|
||||
|
||||
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
|
||||
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -166,8 +166,8 @@ def main():
|
||||
RNS.log("Identity request timed out", RNS.LOG_ERROR)
|
||||
exit(6)
|
||||
else:
|
||||
RNS.log("Received Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash)+" from the network")
|
||||
identity = RNS.Identity.recall(destination_hash)
|
||||
RNS.log("Received Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash)+" from the network")
|
||||
|
||||
else:
|
||||
RNS.log("Recalled Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash))
|
||||
@@ -196,7 +196,7 @@ def main():
|
||||
if args.hash:
|
||||
try:
|
||||
aspects = args.hash.split(".")
|
||||
if not len(aspects) > 1:
|
||||
if not len(aspects) > 0:
|
||||
RNS.log("Invalid destination aspects specified", RNS.LOG_ERROR)
|
||||
exit(32)
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2023 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import RNS
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
|
||||
targetverbosity = verbosity-quietness
|
||||
|
||||
if service:
|
||||
targetlogdest = RNS.LOG_FILE
|
||||
targetverbosity = None
|
||||
else:
|
||||
targetlogdest = RNS.LOG_STDOUT
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest)
|
||||
exit(0)
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Distributed Identity Resolver")
|
||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
parser.add_argument('-q', '--quiet', action='count', default=0)
|
||||
parser.add_argument("--exampleconfig", action='store_true', default=False, help="print verbose configuration example to stdout and exit")
|
||||
parser.add_argument("--version", action="version", version="ir {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.exampleconfig:
|
||||
print(__example_rns_config__)
|
||||
exit()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
program_setup(configdir = configarg, verbosity=args.verbose, quietness=args.quiet)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
|
||||
__example_rns_config__ = '''# This is an example Identity Resolver file.
|
||||
'''
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Regular → Executable
+287
-66
@@ -1,4 +1,4 @@
|
||||
#!python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# MIT License
|
||||
#
|
||||
@@ -25,6 +25,7 @@
|
||||
from time import sleep
|
||||
import argparse
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
@@ -32,6 +33,7 @@ import datetime
|
||||
import time
|
||||
import math
|
||||
import hashlib
|
||||
import zipfile
|
||||
from urllib.request import urlretrieve
|
||||
from importlib import util
|
||||
import RNS
|
||||
@@ -39,7 +41,7 @@ import RNS
|
||||
RNS.logtimefmt = "%H:%M:%S"
|
||||
RNS.compact_log_fmt = True
|
||||
|
||||
program_version = "2.1.0"
|
||||
program_version = "2.1.3"
|
||||
eth_addr = "0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a"
|
||||
btc_addr = "3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq"
|
||||
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
|
||||
@@ -77,6 +79,8 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_DISP_INT = 0x45
|
||||
CMD_DISP_ADR = 0x63
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_BT_PIN = 0x62
|
||||
CMD_BOARD = 0x47
|
||||
@@ -129,6 +133,10 @@ class ROM():
|
||||
MODEL_A2 = 0xA2
|
||||
MODEL_A7 = 0xA7
|
||||
|
||||
PRODUCT_T32_10 = 0xB2
|
||||
MODEL_BA = 0xBA
|
||||
MODEL_BB = 0xBB
|
||||
|
||||
PRODUCT_T32_20 = 0xB0
|
||||
MODEL_B3 = 0xB3
|
||||
MODEL_B8 = 0xB8
|
||||
@@ -180,6 +188,7 @@ products = {
|
||||
ROM.PRODUCT_RNODE: "RNode",
|
||||
ROM.PRODUCT_HMBRW: "Hombrew RNode",
|
||||
ROM.PRODUCT_TBEAM: "LilyGO T-Beam",
|
||||
ROM.PRODUCT_T32_10: "LilyGO LoRa32 v1.0",
|
||||
ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0",
|
||||
ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1",
|
||||
ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2",
|
||||
@@ -207,6 +216,8 @@ models = {
|
||||
0xB8: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v20.zip"],
|
||||
0xB4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21.zip"],
|
||||
0xB9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21.zip"],
|
||||
0xBA: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v10.zip"],
|
||||
0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip"],
|
||||
0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip"],
|
||||
0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip"],
|
||||
0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip"],
|
||||
@@ -226,6 +237,8 @@ try:
|
||||
FWD_DIR = CNF_DIR+"/firmware"
|
||||
EXT_DIR = CNF_DIR+"/extracted"
|
||||
RT_PATH = CNF_DIR+"/recovery_esptool.py"
|
||||
TK_DIR = CNF_DIR+"/trusted_keys"
|
||||
ROM_DIR = CNF_DIR+"/eeprom"
|
||||
|
||||
if not os.path.isdir(CNF_DIR):
|
||||
os.makedirs(CNF_DIR)
|
||||
@@ -235,6 +248,10 @@ try:
|
||||
os.makedirs(FWD_DIR)
|
||||
if not os.path.isdir(EXT_DIR):
|
||||
os.makedirs(EXT_DIR)
|
||||
if not os.path.isdir(TK_DIR):
|
||||
os.makedirs(TK_DIR)
|
||||
if not os.path.isdir(ROM_DIR):
|
||||
os.makedirs(ROM_DIR)
|
||||
|
||||
except Exception as e:
|
||||
print("No access to directory "+str(CNF_DIR)+". This utility needs file system access to store firmware and data files. Cannot continue.")
|
||||
@@ -556,6 +573,20 @@ class RNode():
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending host left command to device")
|
||||
|
||||
def set_display_intensity(self, intensity):
|
||||
data = bytes([intensity & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_INT])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display intensity command to device")
|
||||
|
||||
def set_display_address(self, address):
|
||||
data = bytes([address & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ADR])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display address command to device")
|
||||
|
||||
def enable_bluetooth(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
@@ -800,6 +831,35 @@ class RNode():
|
||||
RNS.log("Could not deserialize local signing key")
|
||||
RNS.log(str(e))
|
||||
|
||||
# Try loading trusted signing key for
|
||||
# validation of devices
|
||||
if os.path.isdir(TK_DIR):
|
||||
for f in os.listdir(TK_DIR):
|
||||
if os.path.isfile(TK_DIR+"/"+f) and f.endswith(".pubkey"):
|
||||
try:
|
||||
file = open(TK_DIR+"/"+f, "rb")
|
||||
public_bytes = file.read()
|
||||
file.close()
|
||||
|
||||
try:
|
||||
public_bytes_hex = RNS.hexrep(public_bytes, delimit=False)
|
||||
|
||||
vendor_keys = []
|
||||
for known in known_keys:
|
||||
vendor_keys.append(known[1])
|
||||
|
||||
if not public_bytes_hex in vendor_keys:
|
||||
local_key_entry = ["LOCAL", public_bytes_hex]
|
||||
known_keys.append(local_key_entry)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not deserialize trusted signing key "+str(f))
|
||||
RNS.log(str(e))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not load trusted signing key"+str(f))
|
||||
|
||||
|
||||
for known in known_keys:
|
||||
vendor = known[0]
|
||||
public_hexrep = known[1]
|
||||
@@ -831,6 +891,9 @@ class RNode():
|
||||
print(" WARNING! This device is NOT verifiable and should NOT be trusted.")
|
||||
print(" Someone could have added privacy-breaking or malicious code to it.")
|
||||
print(" ")
|
||||
print(" Please verify the signing key is present on this machine.")
|
||||
print(" Autogenerated keys will not match another machine's signature.")
|
||||
print(" ")
|
||||
print(" Proceed at your own risk and responsibility! If you created this")
|
||||
print(" device yourself, please read the documentation on how to sign your")
|
||||
print(" device to avoid this warning.")
|
||||
@@ -948,6 +1011,7 @@ def ensure_firmware_file(fw_filename):
|
||||
RNS.log("Failed to retrive latest version information for your board.")
|
||||
RNS.log("Check your internet connection and try again.")
|
||||
RNS.log("If you don't have Internet access currently, use the --fw-version option to manually specify a version.")
|
||||
RNS.log("You can also use --extract to copy the firmware from a known-good RNode of the same model.")
|
||||
exit()
|
||||
|
||||
import shutil
|
||||
@@ -972,6 +1036,7 @@ def ensure_firmware_file(fw_filename):
|
||||
os.makedirs(UPD_DIR+"/"+selected_version)
|
||||
|
||||
if not os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename):
|
||||
RNS.log("Firmware "+UPD_DIR+"/"+selected_version+"/"+fw_filename+" not found.")
|
||||
RNS.log("Downloading missing firmware file: "+fw_filename+" for version "+selected_version)
|
||||
urlretrieve(update_target_url, UPD_DIR+"/"+selected_version+"/"+fw_filename)
|
||||
RNS.log("Firmware file downloaded")
|
||||
@@ -986,6 +1051,7 @@ def ensure_firmware_file(fw_filename):
|
||||
selected_hash = release_info.split()[1]
|
||||
except Exception as e:
|
||||
RNS.log("Could not read locally cached release information.")
|
||||
RNS.log("Ensure "+UPD_DIR+"/"+selected_version+"/"+fw_filename+".version exists and has the correct format and hash.")
|
||||
RNS.log("You can clear the cache with the --clear-cache option and try again.")
|
||||
|
||||
if selected_hash == None:
|
||||
@@ -1076,7 +1142,8 @@ def main():
|
||||
parser.add_argument("-e", "--extract", action="store_true", help="Extract firmware from connected RNode for later use")
|
||||
parser.add_argument("-E", "--use-extracted", action="store_true", help="Use the extracted firmware for autoinstallation or update")
|
||||
parser.add_argument("-C", "--clear-cache", action="store_true", help="Clear locally cached firmware files")
|
||||
|
||||
parser.add_argument("--baud-flash", action="store", metavar="baud_flash", type=str, default="921600", help="Set specific baud rate when flashing device. Default is 921600")
|
||||
|
||||
parser.add_argument("-N", "--normal", action="store_true", help="Switch device to normal mode")
|
||||
parser.add_argument("-T", "--tnc", action="store_true", help="Switch device to TNC mode")
|
||||
|
||||
@@ -1084,6 +1151,9 @@ def main():
|
||||
parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off")
|
||||
parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode")
|
||||
|
||||
parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)")
|
||||
parser.add_argument("--display-addr", action="store", metavar="byte", type=str, default=None, help="Set display address as hex byte (00 - FF)")
|
||||
|
||||
parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode")
|
||||
parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode")
|
||||
parser.add_argument("--txp", action="store", metavar="dBm", type=int, default=None, help="TX power in dBm for TNC mode")
|
||||
@@ -1094,12 +1164,14 @@ def main():
|
||||
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")
|
||||
|
||||
parser.add_argument("-P", "--public", action="store_true", help="Display public part of signing key")
|
||||
parser.add_argument("--trust-key", action="store", metavar="hexbytes", type=str, default=None, help="Public key to trust for device verification")
|
||||
|
||||
parser.add_argument("--version", action="store_true", help="Print program version and exit")
|
||||
|
||||
parser.add_argument("-f", "--flash", action="store_true", help=argparse.SUPPRESS) # Flash firmware and bootstrap EEPROM
|
||||
parser.add_argument("-r", "--rom", action="store_true", help=argparse.SUPPRESS) # Bootstrap EEPROM without flashing firmware
|
||||
parser.add_argument("-k", "--key", action="store_true", help=argparse.SUPPRESS) # Generate a new signing key and exit
|
||||
parser.add_argument("-P", "--public", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key
|
||||
parser.add_argument("-S", "--sign", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key
|
||||
parser.add_argument("-H", "--firmware-hash", action="store", help=argparse.SUPPRESS) # Display public part of signing key
|
||||
parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help=argparse.SUPPRESS) # Platform specification for device bootstrap
|
||||
@@ -1132,6 +1204,11 @@ def main():
|
||||
|
||||
if args.fw_version != None:
|
||||
selected_version = args.fw_version
|
||||
try:
|
||||
check_float = float(selected_version)
|
||||
except ValueError:
|
||||
RNS.log("Selected version \""+selected_version+"\" does not appear to be a number.")
|
||||
exit()
|
||||
|
||||
if args.force_update:
|
||||
force_update = True
|
||||
@@ -1139,7 +1216,7 @@ def main():
|
||||
if args.nocheck:
|
||||
upd_nocheck = True
|
||||
|
||||
if args.public or args.key or args.flash or args.rom or args.autoinstall:
|
||||
if args.public or args.key or args.flash or args.rom or args.autoinstall or args.trust_key:
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
@@ -1150,6 +1227,25 @@ def main():
|
||||
|
||||
clear = lambda: os.system('clear')
|
||||
|
||||
if args.trust_key:
|
||||
try:
|
||||
public_bytes = bytes.fromhex(args.trust_key)
|
||||
try:
|
||||
public_key = load_der_public_key(public_bytes, backend=default_backend())
|
||||
key_hash = hashlib.sha256(public_bytes).hexdigest()
|
||||
RNS.log("Trusting key: "+str(key_hash))
|
||||
f = open(TK_DIR+"/"+str(key_hash)+".pubkey", "wb")
|
||||
f.write(public_bytes)
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not create public key from supplied data. Check that the key format is valid.")
|
||||
RNS.log(str(e))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Invalid key data supplied")
|
||||
exit(0)
|
||||
|
||||
if args.use_extracted and ((args.update and args.port != None) or args.autoinstall):
|
||||
print("")
|
||||
print("You have specified that rnodeconf should use a firmware extracted")
|
||||
@@ -1238,7 +1334,16 @@ def main():
|
||||
else:
|
||||
RNS.log("Could not detect a connected RNode")
|
||||
|
||||
if rnode.provisioned and rnode.signature_valid:
|
||||
if rnode.provisioned:
|
||||
if not rnode.signature_valid:
|
||||
print("\nThe device signature in this RNode is unknown and cannot be verified. It is still")
|
||||
print("possible to extract the firmware from it, but you should make absolutely sure that")
|
||||
print("it comes from a trusted source. It is possible that someone could have modified the")
|
||||
print("firmware. If that is the case, these modifications will propagate to any new RNodes")
|
||||
print("descendent from this one!")
|
||||
print("\nHit enter if you are sure you want to continue.")
|
||||
input()
|
||||
|
||||
if rnode.firmware_hash != None:
|
||||
extracted_hash = rnode.firmware_hash
|
||||
extracted_version = rnode.version
|
||||
@@ -1256,11 +1361,11 @@ def main():
|
||||
hash_f.close()
|
||||
|
||||
extraction_parts = [
|
||||
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
|
||||
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
|
||||
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
|
||||
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
|
||||
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
|
||||
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
|
||||
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
|
||||
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
|
||||
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
|
||||
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
|
||||
]
|
||||
import subprocess, shlex
|
||||
for part, command in extraction_parts:
|
||||
@@ -1387,10 +1492,11 @@ def main():
|
||||
print(" / \\ Select this option if you have put toghether an RNode")
|
||||
print(" | of your own design, or if you are prototyping one.")
|
||||
print("")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0 (aka T3 v1.6)")
|
||||
print("[5] LilyGO T-Beam")
|
||||
print("[6] Heltec LoRa32 v2")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0")
|
||||
print("[5] LilyGO LoRa32 v1.0")
|
||||
print("[6] LilyGO T-Beam")
|
||||
print("[7] Heltec LoRa32 v2")
|
||||
print(" .")
|
||||
print(" / \\ Select one of these options if you want to easily turn")
|
||||
print(" | a supported development board into an RNode.")
|
||||
@@ -1401,7 +1507,7 @@ def main():
|
||||
selected_product = None
|
||||
try:
|
||||
c_dev = int(input())
|
||||
if c_dev < 1 or c_dev > 6:
|
||||
if c_dev < 1 or c_dev > 7:
|
||||
raise ValueError()
|
||||
elif c_dev == 1:
|
||||
selected_product = ROM.PRODUCT_RNODE
|
||||
@@ -1422,7 +1528,7 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 5:
|
||||
elif c_dev == 6:
|
||||
selected_product = ROM.PRODUCT_TBEAM
|
||||
clear()
|
||||
print("")
|
||||
@@ -1452,6 +1558,25 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 5:
|
||||
selected_product = ROM.PRODUCT_T32_10
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" LilyGO LoRa32 v1.0 RNode Installer")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on LoRa32 devices should currently be")
|
||||
print("considered experimental. It is not intended for production or critical use.")
|
||||
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
|
||||
print("who would like to experiment with it.")
|
||||
print("")
|
||||
print("Please Note! This device is known to have a faulty battery charging circuit,")
|
||||
print("which can result in overcharging and damaging batteries. If at all possible,")
|
||||
print("it is recommended to avoid this device.")
|
||||
print("")
|
||||
print("Hit enter if you're sure you wish to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 3:
|
||||
selected_product = ROM.PRODUCT_T32_21
|
||||
clear()
|
||||
@@ -1465,7 +1590,7 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 6:
|
||||
elif c_dev == 7:
|
||||
selected_product = ROM.PRODUCT_H32_V2
|
||||
clear()
|
||||
print("")
|
||||
@@ -1597,6 +1722,28 @@ def main():
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_10:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
print("[1] 433 MHz")
|
||||
print("[2] 868 MHz")
|
||||
print("[3] 915 MHz")
|
||||
print("[4] 923 MHz")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 4:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_BA
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model > 1:
|
||||
selected_model = ROM.MODEL_BB
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_20:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
@@ -1918,10 +2065,10 @@ def main():
|
||||
if fw_filename == "rnode_firmware_tbeam.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -1936,10 +2083,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -1951,13 +2098,49 @@ def main():
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_lora32v10.zip":
|
||||
if numeric_version >= 1.59:
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", 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_lora32v10.boot_app0",
|
||||
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.partitions",
|
||||
]
|
||||
else:
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", 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_lora32v20.boot_app0",
|
||||
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_lora32v20.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -1972,10 +2155,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -1990,10 +2173,10 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_lora32v21.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2008,10 +2191,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2026,10 +2209,10 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_heltec32v2.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2044,10 +2227,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2062,10 +2245,10 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_featheresp32.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2080,10 +2263,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2098,10 +2281,10 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_esp32_generic.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2116,10 +2299,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2134,10 +2317,10 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_ng20.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2152,10 +2335,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2170,10 +2353,10 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_ng21.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2188,10 +2371,10 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2205,10 +2388,10 @@ def main():
|
||||
]
|
||||
elif fw_filename == "extracted_rnode_firmware.zip":
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--baud", args.baud_flash,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
@@ -2276,12 +2459,13 @@ def main():
|
||||
try:
|
||||
if fw_filename.endswith(".zip"):
|
||||
RNS.log("Decompressing firmware...")
|
||||
unzip_status = call(get_flasher_call("unzip", fw_filename))
|
||||
if unzip_status == 0:
|
||||
RNS.log("Firmware decompressed")
|
||||
else:
|
||||
RNS.log("Could not extract firmware from downloaded zip file")
|
||||
try:
|
||||
with zipfile.ZipFile(fw_src+fw_filename) as zip:
|
||||
zip.extractall(fw_src)
|
||||
except Exception as e:
|
||||
RNS.log("Could not decompress firmware from downloaded zip file")
|
||||
exit()
|
||||
RNS.log("Firmware decompressed")
|
||||
|
||||
RNS.log("Flashing RNode firmware to device on "+args.port)
|
||||
from subprocess import call
|
||||
@@ -2295,6 +2479,10 @@ def main():
|
||||
RNS.log("Waiting for ESP32 reset...")
|
||||
time.sleep(7)
|
||||
else:
|
||||
RNS.log("Error from flasher ("+str(flash_status)+") while writing.")
|
||||
RNS.log("Some boards have trouble flashing at high speeds, and you can")
|
||||
RNS.log("try flashing with a lower baud rate, as in this example:")
|
||||
RNS.log("rnodeconf --autoinstall --baud-flash 115200")
|
||||
exit()
|
||||
|
||||
except Exception as e:
|
||||
@@ -2403,13 +2591,15 @@ def main():
|
||||
ensure_firmware_file(fw_filename)
|
||||
|
||||
if fw_filename.endswith(".zip") and not fw_filename == "extracted_rnode_firmware.zip":
|
||||
RNS.log("Extracting firmware...")
|
||||
unzip_status = call(get_flasher_call("unzip", fw_filename))
|
||||
if unzip_status == 0:
|
||||
RNS.log("Firmware extracted")
|
||||
else:
|
||||
RNS.log("Could not extract firmware from downloaded zip file")
|
||||
RNS.log("Decompressing firmware...")
|
||||
fw_src = UPD_DIR+"/"+selected_version+"/"
|
||||
try:
|
||||
with zipfile.ZipFile(fw_src+fw_filename) as zip:
|
||||
zip.extractall(fw_src)
|
||||
except Exception as e:
|
||||
RNS.log("Could not decompress firmware from downloaded zip file")
|
||||
exit()
|
||||
RNS.log("Firmware decompressed")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not obtain firmware package for your board")
|
||||
@@ -2431,7 +2621,11 @@ def main():
|
||||
vf.close()
|
||||
else:
|
||||
partition_filename = fw_filename.replace(".zip", ".bin")
|
||||
partition_hash = get_partition_hash(UPD_DIR+"/"+selected_version+"/"+partition_filename)
|
||||
if fw_filename == "extracted_rnode_firmware.zip":
|
||||
partition_full_path = EXT_DIR+"/extracted_rnode_firmware.bin"
|
||||
else:
|
||||
partition_full_path = UPD_DIR+"/"+selected_version+"/"+partition_filename
|
||||
partition_hash = get_partition_hash(partition_full_path)
|
||||
if partition_hash != None:
|
||||
rnode.set_firmware_hash(partition_hash)
|
||||
rnode.indicate_firmware_update()
|
||||
@@ -2500,7 +2694,7 @@ def main():
|
||||
try:
|
||||
timestamp = time.time()
|
||||
filename = str(time.strftime("%Y-%m-%d_%H-%M-%S"))
|
||||
path = "./eeprom/"+filename+".eeprom"
|
||||
path = ROM_DIR + filename + ".eeprom"
|
||||
file = open(path, "wb")
|
||||
file.write(rnode.eeprom)
|
||||
file.close()
|
||||
@@ -2510,6 +2704,33 @@ def main():
|
||||
RNS.log("but file could not be written to disk.")
|
||||
exit()
|
||||
|
||||
if isinstance(args.display, int):
|
||||
di = args.display
|
||||
if di < 0:
|
||||
di = 0
|
||||
if di > 255:
|
||||
di = 255
|
||||
RNS.log("Setting display intensity to "+str(di))
|
||||
rnode.set_display_intensity(di)
|
||||
|
||||
if isinstance(args.display_addr, str):
|
||||
set_addr = False
|
||||
try:
|
||||
if args.display_addr.startswith("0x"):
|
||||
args.display_addr = args.display_addr[2:]
|
||||
da = bytes.fromhex(args.display_addr)
|
||||
set_addr = True
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if set_addr and len(da) == 1:
|
||||
RNS.log("Setting display address to "+RNS.hexrep(da, delimit=False))
|
||||
rnode.set_display_address(ord(da))
|
||||
rnode.hard_reset()
|
||||
exit()
|
||||
else:
|
||||
RNS.log("Invalid display address specified")
|
||||
|
||||
if args.bluetooth_on:
|
||||
RNS.log("Enabling Bluetooth...")
|
||||
rnode.enable_bluetooth()
|
||||
@@ -2937,4 +3158,4 @@ def extract_recovery_esptool():
|
||||
exit(181)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
+45
-9
@@ -30,7 +30,7 @@ import argparse
|
||||
from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues):
|
||||
def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues, drop_via):
|
||||
if table:
|
||||
destination_hash = None
|
||||
if destination_hexhash != None:
|
||||
@@ -155,6 +155,29 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
elif drop_via:
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(destination_hexhash) != dest_len:
|
||||
raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
|
||||
try:
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid destination entered. Check your input.")
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
|
||||
if reticulum.drop_all_via(destination_hash):
|
||||
print("Dropped all paths via "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("Unable to drop paths via "+RNS.prettyhexrep(destination_hash)+". Does the transport instance exist?")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
else:
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
@@ -187,15 +210,20 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
|
||||
|
||||
if RNS.Transport.has_path(destination_hash):
|
||||
hops = RNS.Transport.hops_to(destination_hash)
|
||||
next_hop = RNS.prettyhexrep(reticulum.get_next_hop(destination_hash))
|
||||
next_hop_interface = reticulum.get_next_hop_if_name(destination_hash)
|
||||
|
||||
if hops != 1:
|
||||
ms = "s"
|
||||
next_hop_bytes = reticulum.get_next_hop(destination_hash)
|
||||
if next_hop_bytes == None:
|
||||
print("\r \rError: Invalid path data returned")
|
||||
sys.exit(1)
|
||||
else:
|
||||
ms = ""
|
||||
next_hop = RNS.prettyhexrep(next_hop_bytes)
|
||||
next_hop_interface = reticulum.get_next_hop_if_name(destination_hash)
|
||||
|
||||
print("\rPath found, destination "+RNS.prettyhexrep(destination_hash)+" is "+str(hops)+" hop"+ms+" away via "+next_hop+" on "+next_hop_interface)
|
||||
if hops != 1:
|
||||
ms = "s"
|
||||
else:
|
||||
ms = ""
|
||||
|
||||
print("\rPath found, destination "+RNS.prettyhexrep(destination_hash)+" is "+str(hops)+" hop"+ms+" away via "+next_hop+" on "+next_hop_interface)
|
||||
else:
|
||||
print("\r \rPath not found")
|
||||
sys.exit(1)
|
||||
@@ -251,6 +279,13 @@ def main():
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-x", "--drop-via",
|
||||
action="store_true",
|
||||
help="drop all paths via specified transport instance",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-w",
|
||||
action="store",
|
||||
@@ -277,7 +312,7 @@ def main():
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if not args.drop_announces and not args.table and not args.rates and not args.destination:
|
||||
if not args.drop_announces and not args.table and not args.rates and not args.destination and not args.drop_via:
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
@@ -291,6 +326,7 @@ def main():
|
||||
verbosity = args.verbose,
|
||||
timeout = args.w,
|
||||
drop_queues = args.drop_announces,
|
||||
drop_via = args.drop_via,
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
+112
-82
@@ -31,8 +31,10 @@ import argparse
|
||||
from RNS._version import __version__
|
||||
|
||||
DEFAULT_PROBE_SIZE = 16
|
||||
DEFAULT_TIMEOUT = 12
|
||||
|
||||
def program_setup(configdir, destination_hexhash, size=DEFAULT_PROBE_SIZE, full_name = None, verbosity = 0):
|
||||
def program_setup(configdir, destination_hexhash, size=None, full_name = None, verbosity = 0, timeout=None, wait=0, probes=1):
|
||||
if size == None: size = DEFAULT_PROBE_SIZE
|
||||
if full_name == None:
|
||||
print("The full destination name including application name aspects must be specified for the destination")
|
||||
exit()
|
||||
@@ -71,14 +73,19 @@ def program_setup(configdir, destination_hexhash, size=DEFAULT_PROBE_SIZE, full_
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
_timeout = time.time() + (timeout or DEFAULT_TIMEOUT+reticulum.get_first_hop_timeout(destination_hash))
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
while not RNS.Transport.has_path(destination_hash) and not time.time() > _timeout:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if time.time() > _timeout:
|
||||
print("\r \rPath request timed out")
|
||||
exit(1)
|
||||
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
request_destination = RNS.Destination(
|
||||
@@ -89,101 +96,120 @@ def program_setup(configdir, destination_hexhash, size=DEFAULT_PROBE_SIZE, full_
|
||||
*aspects
|
||||
)
|
||||
|
||||
probe = RNS.Packet(request_destination, os.urandom(size))
|
||||
receipt = probe.send()
|
||||
sent = 0
|
||||
replies = 0
|
||||
while probes:
|
||||
|
||||
if more_output:
|
||||
more = " via "+RNS.prettyhexrep(reticulum.get_next_hop(destination_hash))+" on "+str(reticulum.get_next_hop_if_name(destination_hash))
|
||||
else:
|
||||
more = ""
|
||||
if sent > 0:
|
||||
time.sleep(wait)
|
||||
|
||||
print("\rSent "+str(size)+" byte probe to "+RNS.prettyhexrep(destination_hash)+more+" ", end=" ")
|
||||
try:
|
||||
probe = RNS.Packet(request_destination, os.urandom(size))
|
||||
probe.pack()
|
||||
except OSError:
|
||||
print("Error: Probe packet size of "+str(len(probe.raw))+" bytes exceed MTU of "+str(RNS.Reticulum.MTU)+" bytes")
|
||||
exit(3)
|
||||
|
||||
i = 0
|
||||
while not receipt.status == RNS.PacketReceipt.DELIVERED:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
receipt = probe.send()
|
||||
sent += 1
|
||||
|
||||
print("\b\b ")
|
||||
sys.stdout.flush()
|
||||
if more_output:
|
||||
nhd = reticulum.get_next_hop(destination_hash)
|
||||
via_str = " via "+RNS.prettyhexrep(nhd) if nhd != None else ""
|
||||
if_str = " on "+str(reticulum.get_next_hop_if_name(destination_hash)) if reticulum.get_next_hop_if_name(destination_hash) != "None" else ""
|
||||
more = via_str+if_str
|
||||
else:
|
||||
more = ""
|
||||
|
||||
hops = RNS.Transport.hops_to(destination_hash)
|
||||
if hops != 1:
|
||||
ms = "s"
|
||||
else:
|
||||
ms = ""
|
||||
print("\rSent probe "+str(sent)+" ("+str(size)+" bytes) to "+RNS.prettyhexrep(destination_hash)+more+" ", end=" ")
|
||||
|
||||
rtt = receipt.get_rtt()
|
||||
if (rtt >= 1):
|
||||
rtt = round(rtt, 3)
|
||||
rttstring = str(rtt)+" seconds"
|
||||
else:
|
||||
rtt = round(rtt*1000, 3)
|
||||
rttstring = str(rtt)+" milliseconds"
|
||||
_timeout = time.time() + (timeout or DEFAULT_TIMEOUT+reticulum.get_first_hop_timeout(destination_hash))
|
||||
i = 0
|
||||
while receipt.status == RNS.PacketReceipt.SENT and not time.time() > _timeout:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
if time.time() > _timeout:
|
||||
print("\r \rProbe timed out")
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
||||
else:
|
||||
print("\b\b ")
|
||||
sys.stdout.flush()
|
||||
|
||||
if receipt.status == RNS.PacketReceipt.DELIVERED:
|
||||
replies += 1
|
||||
hops = RNS.Transport.hops_to(destination_hash)
|
||||
if hops != 1:
|
||||
ms = "s"
|
||||
else:
|
||||
ms = ""
|
||||
|
||||
rtt = receipt.get_rtt()
|
||||
if (rtt >= 1):
|
||||
rtt = round(rtt, 3)
|
||||
rttstring = str(rtt)+" seconds"
|
||||
else:
|
||||
rtt = round(rtt*1000, 3)
|
||||
rttstring = str(rtt)+" milliseconds"
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
||||
reception_q = reticulum.get_packet_q(receipt.proof_packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
||||
|
||||
if reception_q != None:
|
||||
reception_stats += " [Link Quality "+str(reception_q)+"%]"
|
||||
|
||||
else:
|
||||
if receipt.proof_packet != None:
|
||||
if receipt.proof_packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
|
||||
|
||||
if receipt.proof_packet.snr != None:
|
||||
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
|
||||
|
||||
print(
|
||||
"Valid reply from "+
|
||||
RNS.prettyhexrep(receipt.destination.hash)+
|
||||
"\nRound-trip time is "+rttstring+
|
||||
" over "+str(hops)+" hop"+ms+
|
||||
reception_stats+"\n"
|
||||
)
|
||||
|
||||
else:
|
||||
print("\r \rProbe timed out")
|
||||
|
||||
probes -= 1
|
||||
|
||||
loss = round((1-(replies/sent))*100, 2)
|
||||
print(f"Sent {sent}, received {replies}, packet loss {loss}%")
|
||||
if loss > 0:
|
||||
exit(2)
|
||||
else:
|
||||
if receipt.proof_packet != None:
|
||||
if receipt.proof_packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
|
||||
|
||||
if receipt.proof_packet.snr != None:
|
||||
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
|
||||
|
||||
print(
|
||||
"Valid reply received from "+
|
||||
RNS.prettyhexrep(receipt.destination.hash)+
|
||||
"\nRound-trip time is "+rttstring+
|
||||
" over "+str(hops)+" hop"+ms+
|
||||
reception_stats
|
||||
)
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Probe Utility")
|
||||
|
||||
parser.add_argument("--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="rnprobe {version}".format(version=__version__)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"full_name",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="full destination name in dotted notation",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination_hash",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the destination",
|
||||
type=str
|
||||
)
|
||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument("-s", "--size", action="store", default=None, help="size of probe packet payload in bytes", type=int)
|
||||
parser.add_argument("-n", "--probes", action="store", default=1, help="number of probes to send", type=int)
|
||||
parser.add_argument("-t", "--timeout", metavar="seconds", action="store", default=None, help="timeout before giving up", type=float)
|
||||
parser.add_argument("-w", "--wait", metavar="seconds", action="store", default=0, help="time between each probe", type=float)
|
||||
parser.add_argument("--version", action="version", version="rnprobe {version}".format(version=__version__))
|
||||
parser.add_argument("full_name", nargs="?", default=None, help="full destination name in dotted notation", type=str)
|
||||
parser.add_argument("destination_hash", nargs="?", default=None, help="hexadecimal hash of the destination", type=str)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
@@ -202,8 +228,12 @@ def main():
|
||||
program_setup(
|
||||
configdir = configarg,
|
||||
destination_hexhash = args.destination_hash,
|
||||
size = args.size,
|
||||
full_name = args.full_name,
|
||||
verbosity = args.verbose
|
||||
verbosity = args.verbose,
|
||||
probes = args.probes,
|
||||
wait = args.wait,
|
||||
timeout = args.timeout,
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
||||
+27
-4
@@ -30,15 +30,15 @@ from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
targetverbosity = verbosity-quietness
|
||||
|
||||
if service:
|
||||
targetlogdest = RNS.LOG_FILE
|
||||
targetloglevel = None
|
||||
targetverbosity = None
|
||||
else:
|
||||
targetlogdest = RNS.LOG_STDOUT
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel, logdest=targetlogdest)
|
||||
reticulum = RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest)
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
RNS.log("Started rnsd version {version} connected to another shared local instance, this is probably NOT what you want!".format(version=__version__), RNS.LOG_WARNING)
|
||||
else:
|
||||
@@ -87,7 +87,7 @@ __example_rns_config__ = '''# This is an example Reticulum config file.
|
||||
# always-on. This directive is optional and can be removed
|
||||
# for brevity.
|
||||
|
||||
enable_transport = False
|
||||
enable_transport = No
|
||||
|
||||
|
||||
# By default, the first program to launch the Reticulum
|
||||
@@ -111,6 +111,18 @@ share_instance = Yes
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# On systems where running instances may not have access
|
||||
# to the same shared Reticulum configuration directory,
|
||||
# it is still possible to allow full interactivity for
|
||||
# running instances, by manually specifying a shared RPC
|
||||
# key. In almost all cases, this option is not needed, but
|
||||
# it can be useful on operating systems such as Android.
|
||||
# The key must be specified as bytes in hexadecimal.
|
||||
|
||||
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
@@ -120,6 +132,17 @@ instance_control_port = 37429
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
# When Transport is enabled, it is possible to allow the
|
||||
# Transport Instance to respond to probe requests from
|
||||
# the rnprobe utility. This can be a useful tool to test
|
||||
# connectivity. When this option is enabled, the probe
|
||||
# destination will be generated from the Identity of the
|
||||
# Transport Instance, and printed to the log at startup.
|
||||
# Optional, and disabled by default.
|
||||
|
||||
respond_to_probes = No
|
||||
|
||||
|
||||
[logging]
|
||||
# Valid log levels are 0 through 7:
|
||||
# 0: Log only critical information
|
||||
|
||||
+118
-19
@@ -46,7 +46,7 @@ def size_str(num, suffix='B'):
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
|
||||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, sorting=None, sort_reverse=False):
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
|
||||
stats = None
|
||||
@@ -56,7 +56,44 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
pass
|
||||
|
||||
if stats != None:
|
||||
for ifstat in stats["interfaces"]:
|
||||
if json:
|
||||
import json
|
||||
for s in stats:
|
||||
if isinstance(stats[s], bytes):
|
||||
stats[s] = RNS.hexrep(stats[s], delimit=False)
|
||||
|
||||
if isinstance(stats[s], dict):
|
||||
for i in stats[s]:
|
||||
if isinstance(i, dict):
|
||||
for k in i:
|
||||
if isinstance(i[k], bytes):
|
||||
i[k] = RNS.hexrep(i[k], delimit=False)
|
||||
|
||||
print(json.dumps(stats))
|
||||
exit()
|
||||
|
||||
interfaces = stats["interfaces"]
|
||||
if sorting != None and isinstance(sorting, str):
|
||||
sorting = sorting.lower()
|
||||
if sorting == "rate" or sorting == "bitrate":
|
||||
interfaces.sort(key=lambda i: i["bitrate"], reverse=not sort_reverse)
|
||||
if sorting == "rx":
|
||||
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 == "traffic":
|
||||
interfaces.sort(key=lambda i: i["rxb"]+i["txb"], reverse=not sort_reverse)
|
||||
if sorting == "announces" or sorting == "announce":
|
||||
interfaces.sort(key=lambda i: i["incoming_announce_frequency"]+i["outgoing_announce_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "arx":
|
||||
interfaces.sort(key=lambda i: i["incoming_announce_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "atx":
|
||||
interfaces.sort(key=lambda i: i["outgoing_announce_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "held":
|
||||
interfaces.sort(key=lambda i: i["held_announces"], reverse=not sort_reverse)
|
||||
|
||||
|
||||
for ifstat in interfaces:
|
||||
name = ifstat["name"]
|
||||
|
||||
if dispall or not (
|
||||
@@ -98,7 +135,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
else:
|
||||
spec_str = " programs"
|
||||
|
||||
clients_string = "Serving : "+str(cnum)+spec_str
|
||||
clients_string = "Serving : "+str(cnum)+spec_str
|
||||
elif name.startswith("I2PInterface["):
|
||||
if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True:
|
||||
cnum = clients
|
||||
@@ -107,11 +144,11 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
else:
|
||||
spec_str = " connected I2P endpoints"
|
||||
|
||||
clients_string = "Peers : "+str(cnum)+spec_str
|
||||
clients_string = "Peers : "+str(cnum)+spec_str
|
||||
else:
|
||||
clients_string = ""
|
||||
else:
|
||||
clients_string = "Clients : "+str(clients)
|
||||
clients_string = "Clients : "+str(clients)
|
||||
|
||||
else:
|
||||
clients = None
|
||||
@@ -119,43 +156,63 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
print(" {n}".format(n=ifstat["name"]))
|
||||
|
||||
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
|
||||
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
|
||||
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
|
||||
|
||||
print(" Status : {ss}".format(ss=ss))
|
||||
print(" Status : {ss}".format(ss=ss))
|
||||
|
||||
if clients != None and clients_string != "":
|
||||
print(" "+clients_string)
|
||||
|
||||
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
|
||||
print(" Mode : {mode}".format(mode=modestr))
|
||||
print(" Mode : {mode}".format(mode=modestr))
|
||||
|
||||
if "bitrate" in ifstat and ifstat["bitrate"] != None:
|
||||
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
|
||||
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
|
||||
|
||||
if "airtime_short" in ifstat and "airtime_long" in ifstat:
|
||||
print(" Airtime : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
|
||||
|
||||
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"])))
|
||||
|
||||
if "peers" in ifstat and ifstat["peers"] != None:
|
||||
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
|
||||
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
|
||||
|
||||
if "tunnelstate" in ifstat and ifstat["tunnelstate"] != None:
|
||||
print(" I2P : {ts}".format(ts=ifstat["tunnelstate"]))
|
||||
print(" I2P : {ts}".format(ts=ifstat["tunnelstate"]))
|
||||
|
||||
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
|
||||
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
|
||||
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
|
||||
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
|
||||
|
||||
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
|
||||
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
|
||||
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
|
||||
|
||||
if "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
|
||||
if astats and "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
|
||||
aqn = ifstat["announce_queue"]
|
||||
if aqn == 1:
|
||||
print(" Queued : {np} announce".format(np=aqn))
|
||||
print(" Queued : {np} announce".format(np=aqn))
|
||||
else:
|
||||
print(" Queued : {np} announces".format(np=aqn))
|
||||
print(" Queued : {np} announces".format(np=aqn))
|
||||
|
||||
print(" Traffic : {txb}↑\n {rxb}↓".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
|
||||
if astats and "held_announces" in ifstat and ifstat["held_announces"] != None and ifstat["held_announces"] > 0:
|
||||
aqn = ifstat["held_announces"]
|
||||
if aqn == 1:
|
||||
print(" Held : {np} announce".format(np=aqn))
|
||||
else:
|
||||
print(" Held : {np} announces".format(np=aqn))
|
||||
|
||||
if astats and "incoming_announce_frequency" in ifstat and ifstat["incoming_announce_frequency"] != None:
|
||||
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"])))
|
||||
|
||||
if "transport_id" in stats and stats["transport_id"] != None:
|
||||
print("\n Reticulum Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" is running")
|
||||
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
|
||||
if "probe_responder" in stats and stats["probe_responder"] != None:
|
||||
print(" Probe responder at "+RNS.prettyhexrep(stats["probe_responder"])+ " active")
|
||||
print(" Uptime is "+RNS.prettytime(stats["transport_uptime"]))
|
||||
|
||||
print("")
|
||||
|
||||
@@ -175,6 +232,39 @@ def main():
|
||||
help="show all interfaces",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-A",
|
||||
"--announce-stats",
|
||||
action="store_true",
|
||||
help="show announce stats",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--sort",
|
||||
action="store",
|
||||
help="sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]",
|
||||
default=None,
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--reverse",
|
||||
action="store_true",
|
||||
help="reverse sorting",
|
||||
default=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="output in JSON format",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
@@ -187,7 +277,16 @@ def main():
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter)
|
||||
program_setup(
|
||||
configdir = configarg,
|
||||
dispall = args.all,
|
||||
verbosity=args.verbose,
|
||||
name_filter=args.filter,
|
||||
json=args.json,
|
||||
astats=args.announce_stats,
|
||||
sorting=args.sort,
|
||||
sort_reverse=args.reverse,
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
||||
+52
-10
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,10 +32,13 @@ from ._version import __version__
|
||||
from .Reticulum import Reticulum
|
||||
from .Identity import Identity
|
||||
from .Link import Link, RequestReceipt
|
||||
from .Channel import MessageBase
|
||||
from .Buffer import Buffer, RawChannelReader, RawChannelWriter
|
||||
from .Transport import Transport
|
||||
from .Destination import Destination
|
||||
from .Packet import Packet
|
||||
from .Packet import PacketReceipt
|
||||
from .Resolver import Resolver
|
||||
from .Resource import Resource, ResourceAdvertisement
|
||||
from .Cryptography import HKDF
|
||||
from .Cryptography import Hashes
|
||||
@@ -103,7 +106,7 @@ def timestamp_str(time_s):
|
||||
|
||||
def log(msg, level=3, _override_destination = 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
|
||||
@@ -158,6 +161,9 @@ def prettyhexrep(data):
|
||||
hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">"
|
||||
return hexrep
|
||||
|
||||
def prettyspeed(num, suffix="b"):
|
||||
return prettysize(num/8, suffix=suffix)+"ps"
|
||||
|
||||
def prettysize(num, suffix='B'):
|
||||
units = ['','K','M','G','T','P','E','Z']
|
||||
last_unit = 'Y'
|
||||
@@ -177,32 +183,68 @@ def prettysize(num, suffix='B'):
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
|
||||
def prettytime(time, verbose=False):
|
||||
def prettyfrequency(hz, suffix="Hz"):
|
||||
num = hz*1e6
|
||||
units = ["µ", "m", "", "K","M","G","T","P","E","Z"]
|
||||
last_unit = "Y"
|
||||
|
||||
for unit in units:
|
||||
if abs(num) < 1000.0:
|
||||
return "%.2f %s%s" % (num, unit, suffix)
|
||||
num /= 1000.0
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
|
||||
def prettydistance(m, suffix="m"):
|
||||
num = m*1e6
|
||||
units = ["µ", "m", "c", ""]
|
||||
last_unit = "K"
|
||||
|
||||
for unit in units:
|
||||
divisor = 1000.0
|
||||
if unit == "m": divisor = 10
|
||||
if unit == "c": divisor = 100
|
||||
|
||||
if abs(num) < divisor:
|
||||
return "%.2f %s%s" % (num, unit, suffix)
|
||||
num /= divisor
|
||||
|
||||
return "%.2f %s%s" % (num, last_unit, suffix)
|
||||
|
||||
def prettytime(time, verbose=False, compact=False):
|
||||
days = int(time // (24 * 3600))
|
||||
time = time % (24 * 3600)
|
||||
hours = int(time // 3600)
|
||||
time %= 3600
|
||||
minutes = int(time // 60)
|
||||
time %= 60
|
||||
seconds = round(time, 2)
|
||||
if compact:
|
||||
seconds = int(time)
|
||||
else:
|
||||
seconds = round(time, 2)
|
||||
|
||||
ss = "" if seconds == 1 else "s"
|
||||
sm = "" if minutes == 1 else "s"
|
||||
sh = "" if hours == 1 else "s"
|
||||
sd = "" if days == 1 else "s"
|
||||
|
||||
displayed = 0
|
||||
components = []
|
||||
if days > 0:
|
||||
if days > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(days)+" day"+sd if verbose else str(days)+"d")
|
||||
displayed += 1
|
||||
|
||||
if hours > 0:
|
||||
if hours > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(hours)+" hour"+sh if verbose else str(hours)+"h")
|
||||
displayed += 1
|
||||
|
||||
if minutes > 0:
|
||||
if minutes > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(minutes)+" minute"+sm if verbose else str(minutes)+"m")
|
||||
displayed += 1
|
||||
|
||||
if seconds > 0:
|
||||
if seconds > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s")
|
||||
displayed += 1
|
||||
|
||||
i = 0
|
||||
tstr = ""
|
||||
@@ -227,7 +269,7 @@ def phyparams():
|
||||
print("Plaintext Packet MDU : "+str(Packet.PLAIN_MDU)+" bytes")
|
||||
print("Encrypted Packet MDU : "+str(Packet.ENCRYPTED_MDU)+" bytes")
|
||||
print("Link Curve : "+str(Link.CURVE))
|
||||
print("Link Packet MDU : "+str(Packet.ENCRYPTED_MDU)+" bytes")
|
||||
print("Link Packet MDU : "+str(Link.MDU)+" bytes")
|
||||
print("Link Public Key Size : "+str(Link.ECPUBSIZE*8)+" bits")
|
||||
print("Link Private Key Size : "+str(Link.KEYSIZE*8)+" bits")
|
||||
|
||||
@@ -236,4 +278,4 @@ def panic():
|
||||
|
||||
def exit():
|
||||
print("")
|
||||
sys.exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.4.8"
|
||||
__version__ = "0.6.8"
|
||||
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from RNS.vendor.ifaddr._shared import Adapter, IP
|
||||
|
||||
if os.name == "nt":
|
||||
from RNS.vendor.ifaddr._win32 import get_adapters
|
||||
elif os.name == "posix":
|
||||
from RNS.vendor.ifaddr._posix import get_adapters
|
||||
else:
|
||||
raise RuntimeError("Unsupported Operating System: %s" % os.name)
|
||||
|
||||
__all__ = ['Adapter', 'IP', 'get_adapters']
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import os
|
||||
import ctypes.util
|
||||
import ipaddress
|
||||
import collections
|
||||
import socket
|
||||
|
||||
from typing import Iterable, Optional
|
||||
|
||||
import RNS.vendor.ifaddr._shared as shared
|
||||
|
||||
class ifaddrs(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
ifaddrs._fields_ = [
|
||||
('ifa_next', ctypes.POINTER(ifaddrs)),
|
||||
('ifa_name', ctypes.c_char_p),
|
||||
('ifa_flags', ctypes.c_uint),
|
||||
('ifa_addr', ctypes.POINTER(shared.sockaddr)),
|
||||
('ifa_netmask', ctypes.POINTER(shared.sockaddr)),
|
||||
]
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True) # type: ignore
|
||||
|
||||
|
||||
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
|
||||
|
||||
addr0 = addr = ctypes.POINTER(ifaddrs)()
|
||||
retval = libc.getifaddrs(ctypes.byref(addr))
|
||||
if retval != 0:
|
||||
eno = ctypes.get_errno()
|
||||
raise OSError(eno, os.strerror(eno))
|
||||
|
||||
ips = collections.OrderedDict()
|
||||
|
||||
def add_ip(adapter_name: str, ip: Optional[shared.IP]) -> None:
|
||||
if adapter_name not in ips:
|
||||
index = None # type: Optional[int]
|
||||
try:
|
||||
# Mypy errors on this when the Windows CI runs:
|
||||
# error: Module has no attribute "if_nametoindex"
|
||||
index = socket.if_nametoindex(adapter_name) # type: ignore
|
||||
except (OSError, AttributeError):
|
||||
pass
|
||||
ips[adapter_name] = shared.Adapter(adapter_name, adapter_name, [], index=index)
|
||||
if ip is not None:
|
||||
ips[adapter_name].ips.append(ip)
|
||||
|
||||
while addr:
|
||||
name = addr[0].ifa_name.decode(encoding='UTF-8')
|
||||
ip_addr = shared.sockaddr_to_ip(addr[0].ifa_addr)
|
||||
if ip_addr:
|
||||
if addr[0].ifa_netmask and not addr[0].ifa_netmask[0].sa_familiy:
|
||||
addr[0].ifa_netmask[0].sa_familiy = addr[0].ifa_addr[0].sa_familiy
|
||||
netmask = shared.sockaddr_to_ip(addr[0].ifa_netmask)
|
||||
if isinstance(netmask, tuple):
|
||||
netmaskStr = str(netmask[0])
|
||||
prefixlen = shared.ipv6_prefixlength(ipaddress.IPv6Address(netmaskStr))
|
||||
else:
|
||||
assert netmask is not None, f'sockaddr_to_ip({addr[0].ifa_netmask}) returned None'
|
||||
netmaskStr = str('0.0.0.0/' + netmask)
|
||||
prefixlen = ipaddress.IPv4Network(netmaskStr).prefixlen
|
||||
ip = shared.IP(ip_addr, prefixlen, name)
|
||||
add_ip(name, ip)
|
||||
else:
|
||||
if include_unconfigured:
|
||||
add_ip(name, None)
|
||||
addr = addr[0].ifa_next
|
||||
|
||||
libc.freeifaddrs(addr0)
|
||||
|
||||
return ips.values()
|
||||
Vendored
+198
@@ -0,0 +1,198 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import ctypes
|
||||
import socket
|
||||
import ipaddress
|
||||
import platform
|
||||
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
class Adapter(object):
|
||||
"""
|
||||
Represents a network interface device controller (NIC), such as a
|
||||
network card. An adapter can have multiple IPs.
|
||||
|
||||
On Linux aliasing (multiple IPs per physical NIC) is implemented
|
||||
by creating 'virtual' adapters, each represented by an instance
|
||||
of this class. Each of those 'virtual' adapters can have both
|
||||
a IPv4 and an IPv6 IP address.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, nice_name: str, ips: List['IP'], index: Optional[int] = None) -> None:
|
||||
|
||||
#: Unique name that identifies the adapter in the system.
|
||||
#: On Linux this is of the form of `eth0` or `eth0:1`, on
|
||||
#: Windows it is a UUID in string representation, such as
|
||||
#: `{846EE342-7039-11DE-9D20-806E6F6E6963}`.
|
||||
self.name = name
|
||||
|
||||
#: Human readable name of the adpater. On Linux this
|
||||
#: is currently the same as :attr:`name`. On Windows
|
||||
#: this is the name of the device.
|
||||
self.nice_name = nice_name
|
||||
|
||||
#: List of :class:`ifaddr.IP` instances in the order they were
|
||||
#: reported by the system.
|
||||
self.ips = ips
|
||||
|
||||
#: Adapter index as used by some API (e.g. IPv6 multicast group join).
|
||||
self.index = index
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Adapter(name={name}, nice_name={nice_name}, ips={ips}, index={index})".format(
|
||||
name=repr(self.name), nice_name=repr(self.nice_name), ips=repr(self.ips), index=repr(self.index)
|
||||
)
|
||||
|
||||
|
||||
# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format)
|
||||
_IPv4Address = str
|
||||
|
||||
# Type of an IPv6 address (a three-tuple `(ip, flowinfo, scope_id)`)
|
||||
_IPv6Address = Tuple[str, int, int]
|
||||
|
||||
|
||||
class IP(object):
|
||||
"""
|
||||
Represents an IP address of an adapter.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: Union[_IPv4Address, _IPv6Address], network_prefix: int, nice_name: str) -> None:
|
||||
|
||||
#: IP address. For IPv4 addresses this is a string in
|
||||
#: "xxx.xxx.xxx.xxx" format. For IPv6 addresses this
|
||||
#: is a three-tuple `(ip, flowinfo, scope_id)`, where
|
||||
#: `ip` is a string in the usual collon separated
|
||||
#: hex format.
|
||||
self.ip = ip
|
||||
|
||||
#: Number of bits of the IP that represent the
|
||||
#: network. For a `255.255.255.0` netmask, this
|
||||
#: number would be `24`.
|
||||
self.network_prefix = network_prefix
|
||||
|
||||
#: Human readable name for this IP.
|
||||
#: On Linux is this currently the same as the adapter name.
|
||||
#: On Windows this is the name of the network connection
|
||||
#: as configured in the system control panel.
|
||||
self.nice_name = nice_name
|
||||
|
||||
@property
|
||||
def is_IPv4(self) -> bool:
|
||||
"""
|
||||
Returns `True` if this IP is an IPv4 address and `False`
|
||||
if it is an IPv6 address.
|
||||
"""
|
||||
return not isinstance(self.ip, tuple)
|
||||
|
||||
@property
|
||||
def is_IPv6(self) -> bool:
|
||||
"""
|
||||
Returns `True` if this IP is an IPv6 address and `False`
|
||||
if it is an IPv4 address.
|
||||
"""
|
||||
return isinstance(self.ip, tuple)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "IP(ip={ip}, network_prefix={network_prefix}, nice_name={nice_name})".format(
|
||||
ip=repr(self.ip), network_prefix=repr(self.network_prefix), nice_name=repr(self.nice_name)
|
||||
)
|
||||
|
||||
|
||||
if platform.system() == "Darwin" or "BSD" in platform.system():
|
||||
|
||||
# BSD derived systems use marginally different structures
|
||||
# than either Linux or Windows.
|
||||
# I still keep it in `shared` since we can use
|
||||
# both structures equally.
|
||||
|
||||
class sockaddr(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sa_data', ctypes.c_uint8 * 14),
|
||||
]
|
||||
|
||||
class sockaddr_in(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_uint8 * 4),
|
||||
('sin_zero', ctypes.c_uint8 * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_uint8 * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
else:
|
||||
|
||||
class sockaddr(ctypes.Structure): # type: ignore
|
||||
_fields_ = [('sa_familiy', ctypes.c_uint16), ('sa_data', ctypes.c_uint8 * 14)]
|
||||
|
||||
class sockaddr_in(ctypes.Structure): # type: ignore
|
||||
_fields_ = [
|
||||
('sin_familiy', ctypes.c_uint16),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_uint8 * 4),
|
||||
('sin_zero', ctypes.c_uint8 * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure): # type: ignore
|
||||
_fields_ = [
|
||||
('sin6_familiy', ctypes.c_uint16),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_uint8 * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
|
||||
def sockaddr_to_ip(sockaddr_ptr: 'ctypes.pointer[sockaddr]') -> Optional[Union[_IPv4Address, _IPv6Address]]:
|
||||
if sockaddr_ptr:
|
||||
if sockaddr_ptr[0].sa_familiy == socket.AF_INET:
|
||||
ipv4 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in))
|
||||
ippacked = bytes(bytearray(ipv4[0].sin_addr))
|
||||
ip = str(ipaddress.ip_address(ippacked))
|
||||
return ip
|
||||
elif sockaddr_ptr[0].sa_familiy == socket.AF_INET6:
|
||||
ipv6 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in6))
|
||||
flowinfo = ipv6[0].sin6_flowinfo
|
||||
ippacked = bytes(bytearray(ipv6[0].sin6_addr))
|
||||
ip = str(ipaddress.ip_address(ippacked))
|
||||
scope_id = ipv6[0].sin6_scope_id
|
||||
return (ip, flowinfo, scope_id)
|
||||
return None
|
||||
|
||||
|
||||
def ipv6_prefixlength(address: ipaddress.IPv6Address) -> int:
|
||||
prefix_length = 0
|
||||
for i in range(address.max_prefixlen):
|
||||
if int(address) >> i & 1:
|
||||
prefix_length = prefix_length + 1
|
||||
return prefix_length
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from typing import Iterable, List
|
||||
|
||||
import RNS.vendor.ifaddr._shared as shared
|
||||
|
||||
NO_ERROR = 0
|
||||
ERROR_BUFFER_OVERFLOW = 111
|
||||
MAX_ADAPTER_NAME_LENGTH = 256
|
||||
MAX_ADAPTER_DESCRIPTION_LENGTH = 128
|
||||
MAX_ADAPTER_ADDRESS_LENGTH = 8
|
||||
AF_UNSPEC = 0
|
||||
|
||||
|
||||
class SOCKET_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [('lpSockaddr', ctypes.POINTER(shared.sockaddr)), ('iSockaddrLength', wintypes.INT)]
|
||||
|
||||
|
||||
class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
IP_ADAPTER_UNICAST_ADDRESS._fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('Flags', wintypes.DWORD),
|
||||
('Next', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('Address', SOCKET_ADDRESS),
|
||||
('PrefixOrigin', ctypes.c_uint),
|
||||
('SuffixOrigin', ctypes.c_uint),
|
||||
('DadState', ctypes.c_uint),
|
||||
('ValidLifetime', wintypes.ULONG),
|
||||
('PreferredLifetime', wintypes.ULONG),
|
||||
('LeaseLifetime', wintypes.ULONG),
|
||||
('OnLinkPrefixLength', ctypes.c_uint8),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
IP_ADAPTER_ADDRESSES._fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('IfIndex', wintypes.DWORD),
|
||||
('Next', ctypes.POINTER(IP_ADAPTER_ADDRESSES)),
|
||||
('AdapterName', ctypes.c_char_p),
|
||||
('FirstUnicastAddress', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('FirstAnycastAddress', ctypes.c_void_p),
|
||||
('FirstMulticastAddress', ctypes.c_void_p),
|
||||
('FirstDnsServerAddress', ctypes.c_void_p),
|
||||
('DnsSuffix', ctypes.c_wchar_p),
|
||||
('Description', ctypes.c_wchar_p),
|
||||
('FriendlyName', ctypes.c_wchar_p),
|
||||
]
|
||||
|
||||
|
||||
iphlpapi = ctypes.windll.LoadLibrary("Iphlpapi") # type: ignore
|
||||
|
||||
|
||||
def enumerate_interfaces_of_adapter(
|
||||
nice_name: str, address: IP_ADAPTER_UNICAST_ADDRESS
|
||||
) -> Iterable[shared.IP]:
|
||||
|
||||
# Iterate through linked list and fill list
|
||||
addresses = [] # type: List[IP_ADAPTER_UNICAST_ADDRESS]
|
||||
while True:
|
||||
addresses.append(address)
|
||||
if not address.Next:
|
||||
break
|
||||
address = address.Next[0]
|
||||
|
||||
for address in addresses:
|
||||
ip = shared.sockaddr_to_ip(address.Address.lpSockaddr)
|
||||
assert ip is not None, f'sockaddr_to_ip({address.Address.lpSockaddr}) returned None'
|
||||
network_prefix = address.OnLinkPrefixLength
|
||||
yield shared.IP(ip, network_prefix, nice_name)
|
||||
|
||||
|
||||
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
|
||||
|
||||
# Call GetAdaptersAddresses() with error and buffer size handling
|
||||
|
||||
addressbuffersize = wintypes.ULONG(15 * 1024)
|
||||
retval = ERROR_BUFFER_OVERFLOW
|
||||
while retval == ERROR_BUFFER_OVERFLOW:
|
||||
addressbuffer = ctypes.create_string_buffer(addressbuffersize.value)
|
||||
retval = iphlpapi.GetAdaptersAddresses(
|
||||
wintypes.ULONG(AF_UNSPEC),
|
||||
wintypes.ULONG(0),
|
||||
None,
|
||||
ctypes.byref(addressbuffer),
|
||||
ctypes.byref(addressbuffersize),
|
||||
)
|
||||
if retval != NO_ERROR:
|
||||
raise ctypes.WinError() # type: ignore
|
||||
|
||||
# Iterate through adapters fill array
|
||||
address_infos = [] # type: List[IP_ADAPTER_ADDRESSES]
|
||||
address_info = IP_ADAPTER_ADDRESSES.from_buffer(addressbuffer)
|
||||
while True:
|
||||
address_infos.append(address_info)
|
||||
if not address_info.Next:
|
||||
break
|
||||
address_info = address_info.Next[0]
|
||||
|
||||
# Iterate through unicast addresses
|
||||
result = [] # type: List[shared.Adapter]
|
||||
for adapter_info in address_infos:
|
||||
|
||||
# We don't expect non-ascii characters here, so encoding shouldn't matter
|
||||
name = adapter_info.AdapterName.decode()
|
||||
nice_name = adapter_info.Description
|
||||
index = adapter_info.IfIndex
|
||||
|
||||
if adapter_info.FirstUnicastAddress:
|
||||
ips = enumerate_interfaces_of_adapter(
|
||||
adapter_info.FriendlyName, adapter_info.FirstUnicastAddress[0]
|
||||
)
|
||||
ips = list(ips)
|
||||
result.append(shared.Adapter(name, nice_name, ips, index=index))
|
||||
elif include_unconfigured:
|
||||
result.append(shared.Adapter(name, nice_name, [], index=index))
|
||||
|
||||
return result
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
import ipaddress
|
||||
import RNS.vendor.ifaddr
|
||||
import socket
|
||||
|
||||
from typing import List
|
||||
|
||||
AF_INET6 = socket.AF_INET6.value
|
||||
AF_INET = socket.AF_INET.value
|
||||
|
||||
def interfaces() -> List[str]:
|
||||
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
|
||||
return [a.name for a in adapters]
|
||||
|
||||
def ifaddresses(ifname) -> dict:
|
||||
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
|
||||
ifa = {}
|
||||
for a in adapters:
|
||||
if a.name == ifname:
|
||||
ipv4s = []
|
||||
ipv6s = []
|
||||
for ip in a.ips:
|
||||
t = {}
|
||||
if ip.is_IPv4:
|
||||
net = ipaddress.ip_network(str(ip.ip)+"/"+str(ip.network_prefix), strict=False)
|
||||
t["addr"] = ip.ip
|
||||
t["prefix"] = ip.network_prefix
|
||||
t["broadcast"] = str(net.broadcast_address)
|
||||
ipv4s.append(t)
|
||||
if ip.is_IPv6:
|
||||
t["addr"] = ip.ip[0]
|
||||
ipv6s.append(t)
|
||||
|
||||
if len(ipv4s) > 0:
|
||||
ifa[AF_INET] = ipv4s
|
||||
if len(ipv6s) > 0:
|
||||
ifa[AF_INET6] = ipv6s
|
||||
|
||||
return ifa
|
||||
+12
-33
@@ -14,23 +14,14 @@ This document outlines the currently established development roadmap for Reticul
|
||||
## Currently Active Work Areas
|
||||
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
|
||||
|
||||
- The current `0.4.x` release cycle aims at completing:
|
||||
- [x] Improve storage persist call on local client connect/disconnect
|
||||
- [x] Better path invalidation on roaming interfaces
|
||||
- [x] Improved roaming support on Android
|
||||
- [x] Add bluetooth pairing code output to rnodeconf
|
||||
- [x] Add `rnid` utility with encryption, signing and Identity funcionality
|
||||
- [ ] Updating the documentation to reflect recent changes and improvements
|
||||
- [ ] Transit traffic display in rnstatus
|
||||
- [ ] JSON output mode for rnstatus
|
||||
- The current `0.6.x` release cycle aims at completing
|
||||
- [ ] Overhauling and updating the documentation
|
||||
- [ ] Distributed Destination Naming System
|
||||
- [ ] Create a standalone RNS Daemon app for Android
|
||||
- Targets for related applications
|
||||
- [x] Add offline & paper message transport to LXMF
|
||||
- [x] Implement paper messaging in Nomad Network
|
||||
- [x] Implement paper messaging in Sideband
|
||||
- [x] Add spatial and multi-interface roaming support in Sideband
|
||||
- [x] Expand device support in Sideband to support older Android devices
|
||||
- [ ] Add bandwidth-based weighting to LXMF propagation node sync peer prioritisation
|
||||
- [ ] Network-wide path balancing
|
||||
- [ ] Add automatic retries to all use cases of the `Request` API
|
||||
- [ ] Performance and memory optimisations of the Python reference implementation
|
||||
- [ ] Fixing bugs discovered while operating Reticulum systems and applications
|
||||
|
||||
## Primary Efforts
|
||||
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
|
||||
@@ -49,13 +40,12 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
||||
- Update NomadNet screenshots
|
||||
- Update Sideband screenshots
|
||||
- Installation
|
||||
- Install docs for fedora, needs `python3-netifaces`
|
||||
- Add a *Reticulum On Raspberry Pi* section
|
||||
- Update *Reticulum On Android* section if necessary
|
||||
- Update Android install documentation.
|
||||
- [x] Add a *Reticulum On Raspberry Pi* section
|
||||
- [x] Update *Reticulum On Android* section if necessary
|
||||
- [x] Update Android install documentation.
|
||||
- Communications hardware section
|
||||
- Add information about RNode external displays.
|
||||
- Packet radio modems.
|
||||
- [x] Packet radio modems.
|
||||
- Possibly add other relevant types here as well.
|
||||
- Setup *Best Practices For...* / *Installation Examples* section.
|
||||
- Home or office (example)
|
||||
@@ -65,7 +55,6 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
||||
### Universality
|
||||
These efforts seek to broaden the universality of the Reticulum software and hardware ecosystem by continously diversifying platform support, and by improving the overall availability and ease of deployment of the Reticulum stack.
|
||||
|
||||
- Improved roaming support on Android
|
||||
- OpenWRT support
|
||||
- Create a standalone RNS Daemon app for Android
|
||||
- A lightweight and portable C implementation for microcontrollers, µRNS
|
||||
@@ -76,10 +65,7 @@ These efforts seek to broaden the universality of the Reticulum software and har
|
||||
### Functionality
|
||||
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
|
||||
|
||||
- Improve storage persist call on local client connect/disconnect
|
||||
- Faster path invalidation on physical topography changes
|
||||
- Better path invalidation on roaming interfaces
|
||||
- Add a `Buffer` class to the API, for handling stream-like buffers over Reticulum
|
||||
- Add automatic retries to all use cases of the `Request` API
|
||||
- Network-wide path balancing
|
||||
- Distributed Destination Naming System
|
||||
- Globally routable multicast
|
||||
@@ -89,15 +75,9 @@ These efforts aim to expand and improve the core functionality and reliability o
|
||||
### Usability & Utility
|
||||
These effors seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
|
||||
|
||||
- Add bluetooth pairing code output to rnodeconf
|
||||
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
|
||||
- Transit traffic display in rnstatus
|
||||
- JSON output mode for rnstatus
|
||||
- rnid utility
|
||||
- rnsign utility
|
||||
- rncrypt utility
|
||||
- rnsconfig utility
|
||||
- Expand rnx utility to true interactive remote shell
|
||||
|
||||
### Interfaceability
|
||||
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
|
||||
@@ -125,7 +105,6 @@ The Reticulum ecosystem is enriched by several other software and hardware proje
|
||||
This section lists, in no particular order, various important efforts that would be beneficial to the goals of Reticulum.
|
||||
|
||||
- The [RNode](https://unsigned.io/rnode/) project
|
||||
- [x] Evolve RNode into a self-replicating system, so that anyone can use an existing RNode to create more RNodes, and bootstrap functional networks based on Reticulum, even in absence of the Internet.
|
||||
- [ ] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
|
||||
|
||||
## Release History
|
||||
|
||||
@@ -30,3 +30,8 @@ help:
|
||||
cp -r build/latex/reticulumnetworkstack.pdf ./Reticulum\ Manual.pdf; \
|
||||
echo "PDF Manual Generated"; \
|
||||
fi
|
||||
|
||||
@if [ $@ = "epub" ]; then \
|
||||
cp -r build/epub/ReticulumNetworkStack.epub ./Reticulum\ Manual.epub; \
|
||||
echo "EPUB Manual Generated"; \
|
||||
fi
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: ad70b76a21781a545337b16430b5a63a
|
||||
config: 01461792acfce5a3fb667a828829012a
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -92,6 +92,28 @@ The *Request* example explores sendig requests and receiving responses.
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
|
||||
|
||||
.. _example-channel:
|
||||
|
||||
Channel
|
||||
=======
|
||||
|
||||
The *Channel* example explores using a ``Channel`` to send structured
|
||||
data between peers of a ``Link``.
|
||||
|
||||
.. literalinclude:: ../../Examples/Channel.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py>`_.
|
||||
|
||||
Buffer
|
||||
======
|
||||
|
||||
The *Buffer* example explores using buffered readers and writers to send
|
||||
binary data between peers of a ``Link``.
|
||||
|
||||
.. literalinclude:: ../../Examples/Buffer.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py>`_.
|
||||
|
||||
.. _example-filetransfer:
|
||||
|
||||
Filetransfer
|
||||
|
||||
@@ -10,22 +10,45 @@ scenarios.
|
||||
Standalone Reticulum Installation
|
||||
=============================================
|
||||
If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via ``pip``:
|
||||
the easiest way is via the ``pip`` package manager:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
If you no not already have pip installed, you can install it using the package manager
|
||||
If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like ``sudo apt install python3-pip``,
|
||||
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using ``pip``:
|
||||
``sudo pamac install python-pip`` or similar.
|
||||
|
||||
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install ./rns-0.4.6-py3-none-any.whl
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
|
||||
.. code::
|
||||
|
||||
# Debian / Ubuntu / Derivatives
|
||||
sudo apt install build-essential
|
||||
|
||||
# Arch / Manjaro / Derivatives
|
||||
sudo pamac install base-devel
|
||||
|
||||
# Fedora
|
||||
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||
|
||||
With the base development packages installed, ``pip`` should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don't have pre-
|
||||
compiled packages available.
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
|
||||
@@ -42,6 +65,14 @@ transceivers or infrastructure just to try it out. Launching the programs on sep
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.
|
||||
|
||||
Remote Shell
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The `rnsh <https://github.com/acehoss/rnsh>`_ program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how ``ssh`` works. The ``rnsh`` is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links.
|
||||
|
||||
Nomad Network
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
@@ -189,25 +220,25 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
|
||||
|
||||
.. code::
|
||||
|
||||
# TCP/IP interface to the Dublin hub
|
||||
[[RNS Testnet Dublin]]
|
||||
# TCP/IP interface to the RNS Amsterdam Hub
|
||||
[[RNS Testnet Amsterdam]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = dublin.connect.reticulum.network
|
||||
target_host = amsterdam.connect.reticulum.network
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt hub
|
||||
[[RNS Testnet Frankfurt]]
|
||||
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
|
||||
[[RNS Testnet BetweenTheBorders]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
target_port = 5377
|
||||
target_host = betweentheborders.com
|
||||
target_port = 4242
|
||||
|
||||
# Interface to I2P hub A
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
# Interface to Testnet I2P Hub
|
||||
[[RNS Testnet I2P Hub]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = uxg5kubabakh3jtnvsipingbr5574dle7bubvip7llfvwx2tgrua.b32.i2p
|
||||
peers = g3br23bvx3lq5uddcsjii74xgmn6y5q325ovrkq2zw2wbzbqgbuq.b32.i2p
|
||||
|
||||
Many other Reticulum instances are connecting to this testnet, and you can also join it
|
||||
via other entry points if you know them. There is absolutely no control over the network
|
||||
@@ -234,7 +265,7 @@ chapter for a guide. If you prefer purchasing a ready-made unit, you can refer t
|
||||
refer to these additional external resources:
|
||||
|
||||
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
|
||||
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
|
||||
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-supported-devices/>`_
|
||||
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
|
||||
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
|
||||
|
||||
@@ -251,7 +282,7 @@ started is to install the latest release of Reticulum via pip:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install rns
|
||||
pip install rns
|
||||
|
||||
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
|
||||
@@ -261,7 +292,7 @@ For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
pip install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
@@ -276,7 +307,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial netifaces
|
||||
pip install cryptography pyserial
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
@@ -286,25 +317,25 @@ don't use pip, but try this recipe:
|
||||
ln -s ../RNS ./Examples/
|
||||
|
||||
# Run an example
|
||||
python3 Examples/Echo.py -s
|
||||
python Examples/Echo.py -s
|
||||
|
||||
# Unless you've manually created a config file, Reticulum will do so now,
|
||||
# and immediately exit. Make any necessary changes to the file:
|
||||
nano ~/.reticulum/config
|
||||
|
||||
# ... and launch the example again.
|
||||
python3 Examples/Echo.py -s
|
||||
python Examples/Echo.py -s
|
||||
|
||||
# You can now repeat the process on another computer,
|
||||
# and run the same example with -h to get command line options.
|
||||
python3 Examples/Echo.py -h
|
||||
python Examples/Echo.py -h
|
||||
|
||||
# Run the example in client mode to "ping" the server.
|
||||
# Replace the hash below with the actual destination hash of your server.
|
||||
python3 Examples/Echo.py 174a64852a75682259ad8b921b8bf416
|
||||
python Examples/Echo.py 174a64852a75682259ad8b921b8bf416
|
||||
|
||||
# Have a look at another example
|
||||
python3 Examples/Filetransfer.py -h
|
||||
python Examples/Filetransfer.py -h
|
||||
|
||||
When you have experimented with the basic examples, it's time to go read the
|
||||
:ref:`Understanding Reticulum<understanding-main>` chapter. Before submitting
|
||||
@@ -313,30 +344,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Raspberry Pi
|
||||
==============================================
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Android
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
|
||||
|
||||
@@ -402,13 +417,105 @@ It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
|
||||
|
||||
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Debian released after April 2023, it is no longer possible by default
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but will not work
|
||||
for including and using Reticulum in your own scripts and programs.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
Alternatively, you can restore normal behaviour to ``pip`` by creating or editing
|
||||
the configuration file located at ``~/.config/pip/pip.conf``, and adding the
|
||||
following section:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[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.
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Ubuntu released after April 2023, it is no longer possible by default
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but will not work
|
||||
for including and using Reticulum in your own scripts and programs.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
Alternatively, you can restore normal behaviour to ``pip`` by creating or editing
|
||||
the configuration file located at ``~/.config/pip/pip.conf``, and adding the
|
||||
following section:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[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.
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
install one or more dependencies
|
||||
|
||||
On more unusual systems, and in some rare cases, it might not be possible to
|
||||
install or even compile one or more of the above modules. In such situations,
|
||||
install one or more dependencies. In such situations,
|
||||
you can use the ``rnspure`` package instead of the ``rns`` package, or use ``pip``
|
||||
with the ``--no-dependencies`` command-line option. The ``rnspure``
|
||||
package requires no external dependencies for installation. Please note that the
|
||||
|
||||
@@ -24,11 +24,20 @@ starting from scratch.
|
||||
This chapter will outline a few different sensible starting paths to get
|
||||
real-world functional wireless communications up and running with minimal cost
|
||||
and effort. Two fundamental devices categories will be covered, *RNodes* and
|
||||
*WiFi-based radios*.
|
||||
*WiFi-based radios*. Additionally, other common options will be briefly described.
|
||||
|
||||
Knowing how to employ just a few different types of hardware will make it possible
|
||||
to build a wide range of useful networks with little effort.
|
||||
|
||||
Combining Hardware Types
|
||||
========================
|
||||
|
||||
It is useful to combine different link and hardware types when designing and
|
||||
building a network. One useful design pattern is to employ high-capacity point-to-point
|
||||
links based on WiFi or millimeter-wave radios (with high-gain directional antennas)
|
||||
for the network backbone, and using LoRa-based RNodes for covering large areas with
|
||||
connectivity for client devices.
|
||||
|
||||
While there are many other device categories that are useful in building Reticulum
|
||||
networks, knowing how to employ just these two will make it possible to build
|
||||
a wide range of useful networks with little effort.
|
||||
|
||||
.. _rnode-main:
|
||||
|
||||
@@ -190,13 +199,6 @@ such as serial port and on-air parameters. For v2.x firmwares, you just need to
|
||||
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
|
||||
RNode, using the parameters stored in the RNode itself.
|
||||
|
||||
.. _rnode-suppliers:
|
||||
|
||||
Suppliers
|
||||
^^^^^^^^^
|
||||
Get in touch if you want to have your RNode supplier listed here, or if you want help to
|
||||
get started with producing RNodes.
|
||||
|
||||
|
||||
WiFi-based Hardware
|
||||
===================
|
||||
@@ -231,11 +233,31 @@ that is relatively cheap while providing long range and high capacity for Reticu
|
||||
networks. As in all other cases, it is also possible for Reticulum to co-exist with IP
|
||||
networks running concurrently on such devices.
|
||||
|
||||
Combining Hardware Types
|
||||
========================
|
||||
Ethernet-based Hardware
|
||||
=======================
|
||||
|
||||
It is useful to combine different link and hardware types when designing and
|
||||
building a network. One useful design pattern is to employ high-capacity point-to-point
|
||||
links based on WiFi or millimeter-wave radios (with high-gain directional antennas)
|
||||
for the network backbone, and using LoRa-based RNodes for covering large areas with
|
||||
connectivity for client devices.
|
||||
Reticulum can run over any kind of hardware that can provide a switched Ethernet-based
|
||||
medium. This means that anything from a plain Ethernet switch, to fiber-optic systems,
|
||||
to data radios with Ethernet interfaces can be used by Reticulum.
|
||||
|
||||
The Ethernet medium does not need to have any IP infrastructure such as DHCP servers
|
||||
or routing set up, but in case such infrastructure does exist, Reticulum will simply
|
||||
co-exist with.
|
||||
|
||||
To use Reticulum over Ethernet-based mediums, it is generally enough to use the included
|
||||
:ref:`AutoInterface<interfaces-auto>`. This interface also works over any kind of
|
||||
virtual networking adapter, such as ``tun`` and ``tap`` devices in Linux.
|
||||
|
||||
Serial Lines & Devices
|
||||
======================
|
||||
|
||||
Using Reticulum over any kind of raw serial line is also possible with the
|
||||
:ref:`SerialInterface<interfaces-serial>`. This interface type is also useful for
|
||||
using Reticulum over communications hardware that provides a serial port interface.
|
||||
|
||||
Packet Radio Modems
|
||||
===================
|
||||
|
||||
Any packet radio modem that provides a standard KISS interface over USB, serial or TCP
|
||||
can be used with Reticulum. This includes virtual software modems such as
|
||||
`FreeDV TNC <https://github.com/xssfox/freedv-tnc>`_ and `Dire Wolf <https://github.com/wb2osz/direwolf>`_.
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
******************************
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
|
||||
.. only:: builder_html
|
||||
|
||||
Table Of Contents
|
||||
=================
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
.. _interfaces-main:
|
||||
|
||||
********************
|
||||
Supported Interfaces
|
||||
********************
|
||||
**********************
|
||||
Configuring Interfaces
|
||||
**********************
|
||||
|
||||
Reticulum supports using many kinds of devices as networking interfaces, and
|
||||
allows you to mix and match them in any way you choose. The number of distinct
|
||||
@@ -365,6 +365,7 @@ can be used, and offers full control over LoRa parameters.
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters.
|
||||
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
@@ -372,7 +373,21 @@ can be used, and offers full control over LoRa parameters.
|
||||
# with low amounts of RAM, using packet
|
||||
# flow control can be useful. By default
|
||||
# it is disabled.
|
||||
flow_control = False
|
||||
|
||||
# flow_control = False
|
||||
|
||||
# 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 = 1.5
|
||||
# airtime_limit_short = 33
|
||||
|
||||
|
||||
.. _interfaces-serial:
|
||||
|
||||
@@ -746,3 +761,74 @@ conserve bandwidth, while very fast networks can support applications that
|
||||
need very frequent announces. Reticulum implements these mechanisms to ensure
|
||||
that a large span of network types can seamlessly *co-exist* and interconnect.
|
||||
|
||||
.. _interfaces-ingress-control:
|
||||
|
||||
New Destination Rate Limiting
|
||||
=============================
|
||||
|
||||
On public interfaces, where anyone may connect and announce new destinations,
|
||||
it can be useful to control the rate at which announces for *new* destinations are
|
||||
processed.
|
||||
|
||||
If a large influx of announces for newly created or previously unknown destinations
|
||||
occur within a short amount of time, Reticulum will place these announces on hold,
|
||||
so that announce traffic for known and previously established destinations can
|
||||
continue to be processed without interruptions.
|
||||
|
||||
After the burst subsides, and an additional waiting period has passed, the held
|
||||
announces will be released at a slow rate, until the hold queue is cleared. This
|
||||
also means, that should a node decide to connect to a public interface, announce
|
||||
a large amount of bogus destinations, and then disconnect, these destination will
|
||||
never make it into path tables and waste network bandwidth on retransmitted
|
||||
announces.
|
||||
|
||||
**It's important to note** that the ingress control works at the level of *individual
|
||||
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface will still have new announces
|
||||
processed without interruption.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress announce
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
* | The ``ingress_control`` option tells Reticulum whether or not
|
||||
to enable announce ingress control on the interface. Defaults to
|
||||
``True``.
|
||||
|
||||
* | The ``ic_new_time`` option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
|
||||
option is useful on publicly accessible interfaces that spawn new
|
||||
sub-interfaces when a new client connects.
|
||||
|
||||
* | The ``ic_burst_freq_new`` option sets the maximum announce ingress
|
||||
frequency for newly spawned interfaces. Defaults to ``3.5``
|
||||
announces per second.
|
||||
|
||||
* | The ``ic_burst_freq`` option sets the maximum announce ingress
|
||||
frequency for other interfaces. Defaults to ``12`` announces
|
||||
per second.
|
||||
|
||||
*If an interface exceeds its burst frequency, incoming announces
|
||||
for unknown destinations will be temporarily held in a queue, and
|
||||
not processed until later.*
|
||||
|
||||
* | The ``ic_max_held_announces`` option sets the maximum amount of
|
||||
unique announces that will be held in the queue. Any additional
|
||||
unique announces will be dropped. Defaults to ``256`` announces.
|
||||
|
||||
* | The ``ic_burst_hold`` option sets how much time (in seconds) must
|
||||
pass after the burst frequency drops below its threshold, for the
|
||||
announce burst to be considered cleared. Defaults to ``60``
|
||||
seconds.
|
||||
|
||||
* | The ``ic_burst_penalty`` option sets how much time (in seconds) must
|
||||
pass after the burst is considered cleared, before held announces can
|
||||
start being released from the queue. Defaults to ``5*60``
|
||||
seconds.
|
||||
|
||||
* | The ``ic_held_release_interval`` option sets how much time (in seconds)
|
||||
must pass between releasing each held announce from the queue. Defaults
|
||||
to ``30`` seconds.
|
||||
|
||||
|
||||
@@ -121,6 +121,76 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
|
||||
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
|
||||
:members:
|
||||
|
||||
.. _api-channel:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| Channel |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
Channel
|
||||
-------
|
||||
|
||||
.. autoclass:: RNS.Channel.Channel()
|
||||
:members:
|
||||
|
||||
.. _api-messsagebase:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| MessageBase |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
MessageBase
|
||||
-----------
|
||||
|
||||
.. autoclass:: RNS.MessageBase()
|
||||
:members:
|
||||
|
||||
.. _api-buffer:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| Buffer |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
Buffer
|
||||
------
|
||||
|
||||
.. autoclass:: RNS.Buffer
|
||||
:members:
|
||||
|
||||
.. _api-rawchannelreader:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| RawChannelReader |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
RawChannelReader
|
||||
----------------
|
||||
|
||||
.. autoclass:: RNS.RawChannelReader
|
||||
:members: __init__, add_ready_callback, remove_ready_callback
|
||||
|
||||
.. _api-rawchannelwriter:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| RawChannelWriter |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
RawChannelWriter
|
||||
----------------
|
||||
|
||||
.. autoclass:: RNS.RawChannelWriter
|
||||
:members: __init__
|
||||
|
||||
.. _api-transport:
|
||||
|
||||
.. only:: html
|
||||
|
||||
@@ -75,7 +75,7 @@ guide the design of Reticulum:
|
||||
it can be easily modified and replicated by anyone interested in doing so.
|
||||
* **Very low bandwidth requirements**
|
||||
Reticulum should be able to function reliably over links with a transmission capacity as low
|
||||
as *500 bits per second*.
|
||||
as *5 bits per second*.
|
||||
* **Encryption by default**
|
||||
Reticulum must use strong encryption by default for all communication.
|
||||
* **Initiator Anonymity**
|
||||
@@ -595,7 +595,7 @@ or less any medium that allows you to send and receive data, which satisfies som
|
||||
minimum requirements.
|
||||
|
||||
The communication channel must support at least half-duplex operation, and provide an average
|
||||
throughput of around 500 bits per second, and supports a physical layer MTU of 500 bytes. The
|
||||
throughput of 5 bits per second or greater, and supports a physical layer MTU of 500 bytes. The
|
||||
Reticulum stack should be able to run on more or less any hardware that can provide a Python 3.x
|
||||
runtime environment.
|
||||
|
||||
@@ -858,7 +858,7 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
|
||||
* Ed25519 for signatures
|
||||
|
||||
* X22519 for ECDH key exchanges
|
||||
* X25519 for ECDH key exchanges
|
||||
|
||||
* HKDF for key derivation
|
||||
|
||||
|
||||
@@ -99,6 +99,17 @@ configuration file is created. The default configuration looks like this:
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# On systems where running instances may not have access
|
||||
# to the same shared Reticulum configuration directory,
|
||||
# it is still possible to allow full interactivity for
|
||||
# running instances, by manually specifying a shared RPC
|
||||
# key. In almost all cases, this option is not needed, but
|
||||
# it can be useful on operating systems such as Android.
|
||||
# The key must be specified as bytes in hexadecimal.
|
||||
|
||||
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
@@ -108,6 +119,17 @@ configuration file is created. The default configuration looks like this:
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
# When Transport is enabled, it is possible to allow the
|
||||
# Transport Instance to respond to probe requests from
|
||||
# the rnprobe utility. This can be a useful tool to test
|
||||
# connectivity. When this option is enabled, the probe
|
||||
# destination will be generated from the Identity of the
|
||||
# Transport Instance, and printed to the log at startup.
|
||||
# Optional, and disabled by default.
|
||||
|
||||
respond_to_probes = No
|
||||
|
||||
|
||||
[logging]
|
||||
# Valid log levels are 0 through 7:
|
||||
# 0: Log only critical information
|
||||
@@ -145,10 +167,19 @@ configuration file is created. The default configuration looks like this:
|
||||
If Reticulum infrastructure already exists locally, you probably don't need to
|
||||
change anything, and you may already be connected to a wider network. If not,
|
||||
you will probably need to add relevant *interfaces* to the configuration, in
|
||||
order to communicate with other systems. It is a good idea to read the comments
|
||||
and explanations in the above default config. It will teach you the basic
|
||||
concepts you need to understand to configure your network. Once you have done that,
|
||||
take a look at the :ref:`Interfaces<interfaces-main>` chapter of this manual.
|
||||
order to communicate with other systems.
|
||||
|
||||
You can generate a much more verbose configuration example by running the command:
|
||||
|
||||
``rnsd --exampleconfig``
|
||||
|
||||
The output includes examples for most interface types supported
|
||||
by Reticulum, along with additional options and configuration parameters.
|
||||
|
||||
It is a good idea to read the comments and explanations in the above default config.
|
||||
It will teach you the basic concepts you need to understand to configure your network.
|
||||
Once you have done that, take a look at the :ref:`Interfaces<interfaces-main>` chapter
|
||||
of this manual.
|
||||
|
||||
Included Utility Programs
|
||||
-------------------------
|
||||
@@ -170,28 +201,47 @@ When ``rnsd`` is running, it will keep all configured interfaces open, handle tr
|
||||
it is enabled, and allow any other programs to immediately utilise the
|
||||
Reticulum network it is configured for.
|
||||
|
||||
You can even run multiple instances of rnsd with different configurations on
|
||||
You can even run multiple instances of ``rnsd`` with different configurations on
|
||||
the same system.
|
||||
|
||||
.. code:: text
|
||||
**Usage Examples**
|
||||
|
||||
# Install Reticulum
|
||||
pip3 install rns
|
||||
|
||||
# Run rnsd
|
||||
rnsd
|
||||
Run ``rnsd``:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
$ rnsd
|
||||
|
||||
[2023-08-18 17:59:56] [Notice] Started rnsd version 0.5.8
|
||||
|
||||
Run ``rnsd`` in service mode, ensuring all logging output is sent directly to file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnsd -s
|
||||
|
||||
Generate a verbose and detailed configuration example, with explanations of all the
|
||||
various configuration options, and interface configuration examples:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnsd --exampleconfig
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnsd.py [-h] [--config CONFIG] [-v] [-q] [-s] [--exampleconfig] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
-s, --service rnsd is running as a service and should log to file
|
||||
--exampleconfig print verbose configuration example to stdout and exit
|
||||
--version show program's version number and exit
|
||||
|
||||
You can easily add ``rnsd`` as an always-on service by :ref:`configuring a service<using-systemd>`.
|
||||
@@ -202,12 +252,14 @@ The rnstatus Utility
|
||||
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the ``ifconfig`` program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
Run ``rnstatus``:
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnstatus
|
||||
rnstatus
|
||||
$ rnstatus
|
||||
|
||||
# Example output
|
||||
Shared Instance[37428]
|
||||
Status : Up
|
||||
Serving : 1 program
|
||||
@@ -223,7 +275,7 @@ interfaces, similar to the ``ifconfig`` program.
|
||||
Traffic : 63.23 KB↑
|
||||
80.17 KB↓
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
|
||||
Status : Up
|
||||
Mode : Full
|
||||
Rate : 10.00 Mbps
|
||||
@@ -240,44 +292,175 @@ interfaces, similar to the ``ifconfig`` program.
|
||||
|
||||
Reticulum Transport Instance <5245a8efe1788c6a1cd36144a270e13b> running
|
||||
|
||||
Filter output to only show some interfaces:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-v]
|
||||
$ rnstatus rnode
|
||||
|
||||
RNodeInterface[RNode UHF]
|
||||
Status : Up
|
||||
Mode : Access Point
|
||||
Rate : 1.30 kbps
|
||||
Access : 64-bit IFAC by <…e702c42ba8>
|
||||
Traffic : 8.49 KB↑
|
||||
9.23 KB↓
|
||||
|
||||
Reticulum Transport Instance <5245a8efe1788c6a1cd36144a270e13b> running
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
|
||||
[-r] [-j] [-v] [filter]
|
||||
|
||||
Reticulum Network Stack Status
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
positional arguments:
|
||||
filter only display interfaces with names including filter
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
|
||||
-r, --reverse reverse sorting
|
||||
-j, --json output in JSON format
|
||||
-v, --verbose
|
||||
|
||||
|
||||
The rnid Utility
|
||||
====================
|
||||
|
||||
With the ``rnid`` utility, you can generate, manage and view Reticulum Identities.
|
||||
The program can also calculate Destination hashes, and perform encryption and
|
||||
decryption of files.
|
||||
|
||||
Using ``rnid``, it is possible to asymmetrically encrypt files and information for
|
||||
any Reticulum destination hash, and also to create and verify cryptographic signatures.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
Generate a new Identity:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnid -g ./new_identity
|
||||
|
||||
Display Identity key information:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnid -i ./new_identity -p
|
||||
|
||||
Loaded Identity <984b74a3f768bef236af4371e6f248cd> from new_id
|
||||
Public Key : 0f4259fef4521ab75a3409e353fe9073eb10783b4912a6a9937c57bf44a62c1e
|
||||
Private Key : Hidden
|
||||
|
||||
Encrypt a file for an LXMF user:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnid -i 8dd57a738226809646089335a6b03695 -e my_file.txt
|
||||
|
||||
Recalled Identity <bc7291552be7a58f361522990465165c> for destination <8dd57a738226809646089335a6b03695>
|
||||
Encrypting my_file.txt
|
||||
File my_file.txt encrypted for <bc7291552be7a58f361522990465165c> to my_file.txt.rfe
|
||||
|
||||
If the Identity for the destination is not already known, you can fetch it from the network by using the ``-R`` command-line option:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnid -R -i 30602def3b3506a28ed33db6f60cc6c9 -e my_file.txt
|
||||
|
||||
Requesting unknown Identity for <30602def3b3506a28ed33db6f60cc6c9>...
|
||||
Received Identity <2b489d06eaf7c543808c76a5332a447d> for destination <30602def3b3506a28ed33db6f60cc6c9> from the network
|
||||
Encrypting my_file.txt
|
||||
File my_file.txt encrypted for <2b489d06eaf7c543808c76a5332a447d> to my_file.txt.rfe
|
||||
|
||||
Decrypt a file using the Reticulum Identity it was encrypted for:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnid -i ./my_identity -d my_file.txt.rfe
|
||||
|
||||
Loaded Identity <2225fdeecaf6e2db4556c3c2d7637294> from ./my_identity
|
||||
Decrypting ./my_file.txt.rfe...
|
||||
File ./my_file.txt.rfe decrypted with <2225fdeecaf6e2db4556c3c2d7637294> to ./my_file.txt
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnid.py [-h] [--config path] [-i identity] [-g path] [-v] [-q] [-a aspects]
|
||||
[-H aspects] [-e path] [-d path] [-s path] [-V path] [-r path] [-w path]
|
||||
[-f] [-R] [-t seconds] [-p] [-P] [--version]
|
||||
|
||||
Reticulum Identity & Encryption Utility
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config path path to alternative Reticulum config directory
|
||||
-i identity, --identity identity
|
||||
hexadecimal Reticulum Destination hash or path to Identity file
|
||||
-g path, --generate path
|
||||
generate a new Identity
|
||||
-v, --verbose increase verbosity
|
||||
-q, --quiet decrease verbosity
|
||||
-a aspects, --announce aspects
|
||||
announce a destination based on this Identity
|
||||
-H aspects, --hash aspects
|
||||
show destination hashes for other aspects for this Identity
|
||||
-e path, --encrypt path
|
||||
encrypt file
|
||||
-d path, --decrypt path
|
||||
decrypt file
|
||||
-s path, --sign path sign file
|
||||
-V path, --validate path
|
||||
validate signature
|
||||
-r path, --read path input file path
|
||||
-w path, --write path
|
||||
output file path
|
||||
-f, --force write output even if it overwrites existing files
|
||||
-R, --request request unknown Identities from the network
|
||||
-t seconds identity request timeout before giving up
|
||||
-p, --print-identity print identity info and exit
|
||||
-P, --print-private allow displaying private keys
|
||||
--version show program's version number and exit
|
||||
|
||||
|
||||
The rnpath Utility
|
||||
====================
|
||||
|
||||
With the ``rnpath`` utility, you can look up and view paths for
|
||||
destinations on the Reticulum network.
|
||||
|
||||
.. code:: text
|
||||
**Usage Examples**
|
||||
|
||||
# Run rnpath
|
||||
rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
|
||||
# Example output
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
|
||||
Resolve path to a destination:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
|
||||
|
||||
$ rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
|
||||
[-x] [-w seconds] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
|
||||
positional arguments:
|
||||
destination hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
@@ -285,6 +468,7 @@ destinations on the Reticulum network.
|
||||
-r, --rates show announce rate info
|
||||
-d, --drop remove the path to a destination
|
||||
-D, --drop-announces drop all queued announces
|
||||
-x, --drop-via drop all paths via specified transport instance
|
||||
-w seconds timeout before giving up
|
||||
-v, --verbose
|
||||
|
||||
@@ -295,21 +479,53 @@ The rnprobe Utility
|
||||
The ``rnprobe`` utility lets you probe a destination for connectivity, similar
|
||||
to the ``ping`` program. Please note that probes will only be answered if the
|
||||
specified destination is configured to send proofs for received packets. Many
|
||||
destinations will not have this option enabled, and will not be probable.
|
||||
destinations will not have this option enabled, so most destinations will not
|
||||
be probable.
|
||||
|
||||
You can enable a probe-reply destination on Reticulum Transport Instances by
|
||||
setting the ``respond_to_probes`` configuration directive. Reticulum will then
|
||||
print the probe destination to the log on Transport Instance startup.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
Probe a destination:
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnprobe
|
||||
rnprobe example_utilities.echo.request 2d03725b327348980d570f739a3a5708
|
||||
$ rnprobe rnstransport.probe 2d03725b327348980d570f739a3a5708
|
||||
|
||||
# Example output
|
||||
Sent 16 byte probe to <2d03725b327348980d570f739a3a5708>
|
||||
Valid reply received from <2d03725b327348980d570f739a3a5708>
|
||||
Round-trip time is 38.469 milliseconds over 2 hops
|
||||
|
||||
Send a larger probe:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
|
||||
$ rnprobe rnstransport.probe 2d03725b327348980d570f739a3a5708 -s 256
|
||||
|
||||
Sent 16 byte probe to <2d03725b327348980d570f739a3a5708>
|
||||
Valid reply received from <2d03725b327348980d570f739a3a5708>
|
||||
Round-trip time is 38.781 milliseconds over 2 hops
|
||||
|
||||
If the interface that receives the probe replies supports reporting radio
|
||||
parameters such as **RSSI** and **SNR**, the ``rnprobe`` utility will print
|
||||
these as part of the result as well.
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnprobe rnstransport.probe e7536ee90bd4a440e130490b87a25124
|
||||
|
||||
Sent 16 byte probe to <e7536ee90bd4a440e130490b87a25124>
|
||||
Valid reply received from <e7536ee90bd4a440e130490b87a25124>
|
||||
Round-trip time is 1.809 seconds over 1 hop [RSSI -73 dBm] [SNR 12.0 dB]
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
|
||||
[full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
@@ -320,6 +536,7 @@ destinations will not have this option enabled, and will not be probable.
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-s SIZE, --size SIZE size of probe packet payload in bytes
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
|
||||
@@ -330,20 +547,39 @@ The rncp Utility
|
||||
The ``rncp`` utility is a simple file transfer tool. Using it, you can transfer
|
||||
files through Reticulum.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
Run rncp on the receiving system, specifying which identities are allowed to send files:
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rncp on the receiving system, specifying which identities
|
||||
# are allowed to send files
|
||||
rncp --receive -a 1726dbad538775b5bf9b0ea25a4079c8 -a c50cc4e4f7838b6c31f60ab9032cbc62
|
||||
$ rncp --listen -a 1726dbad538775b5bf9b0ea25a4079c8 -a c50cc4e4f7838b6c31f60ab9032cbc62
|
||||
|
||||
# From another system, copy a file to the receiving system
|
||||
rncp ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
|
||||
|
||||
You can specify as many allowed senders as needed, or complete disable authentication.
|
||||
You can also specify allowed identity hashes (one per line) in the file ~/.rncp/allowed_identities
|
||||
and simply running the program in listener mode:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
|
||||
$ rncp --listen
|
||||
|
||||
From another system, copy a file to the receiving system:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rncp ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
|
||||
|
||||
Or fetch a file from the remote system:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rncp --fetch ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
|
||||
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
|
||||
|
||||
Reticulum File Transfer Utility
|
||||
|
||||
@@ -351,19 +587,20 @@ You can specify as many allowed senders as needed, or complete disable authentic
|
||||
file file to be transferred
|
||||
destination hexadecimal hash of the receiver
|
||||
|
||||
optional arguments:
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config path path to alternative Reticulum config directory
|
||||
-v, --verbose increase verbosity
|
||||
-q, --quiet decrease verbosity
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-r, --receive wait for incoming files
|
||||
-b, --no-announce don't announce at program start
|
||||
-S, --silent disable transfer progress output
|
||||
-l, --listen listen for incoming transfer requests
|
||||
-f, --fetch fetch file from remote listener instead of sending
|
||||
-b seconds announce interval, 0 to only announce at startup
|
||||
-a allowed_hash accept from this identity
|
||||
-n, --no-auth accept files from anyone
|
||||
-n, --no-auth accept files and fetches from anyone
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-w seconds sender timeout before giving up
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
|
||||
|
||||
The rnx Utility
|
||||
@@ -371,32 +608,43 @@ The rnx Utility
|
||||
|
||||
The ``rnx`` utility is a basic remote command execution program. It allows you to
|
||||
execute commands on remote systems over Reticulum, and to view returned command
|
||||
output.
|
||||
output. For a fully interactive remote shell solution, be sure to also take a look
|
||||
at the `rnsh <https://github.com/acehoss/rnsh>`_ program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
Run rnx on the listening system, specifying which identities are allowed to execute commands:
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnx on the listening system, specifying which identities
|
||||
# are allowed to execute commands
|
||||
rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
$ rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
|
||||
# From another system, run a command
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
|
||||
# Or enter the interactive mode pseudo-shell
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 -x
|
||||
|
||||
# The default identity file is stored in
|
||||
# ~/.reticulum/identities/rnx, but you can use
|
||||
# another one, which will be created if it does
|
||||
# not already exist
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 -i /path/to/identity -x
|
||||
|
||||
You can specify as many allowed senders as needed, or completely disable authentication.
|
||||
From another system, run a command on the remote:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
|
||||
[destination] [command]
|
||||
$ rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
|
||||
Or enter the interactive mode pseudo-shell:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnx 7a55144adf826958a9529a3bcf08b149 -x
|
||||
|
||||
The default identity file is stored in ``~/.reticulum/identities/rnx``, but you can use
|
||||
another one, which will be created if it does not already exist
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rnx 7a55144adf826958a9529a3bcf08b149 -i /path/to/identity -x
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-n] [-N]
|
||||
[-d] [-m] [-a allowed_hash] [-w seconds] [-W seconds] [--stdin STDIN]
|
||||
[--stdout STDOUT] [--stderr STDERR] [--version] [destination] [command]
|
||||
|
||||
Reticulum Remote Execution Utility
|
||||
|
||||
@@ -433,11 +681,19 @@ The rnodeconf Utility
|
||||
The ``rnodeconf`` utility allows you to inspect and configure existing :ref:`RNodes<rnode-main>`, and
|
||||
to create and provision new :ref:`RNodes<rnode-main>` from any supported hardware devices.
|
||||
|
||||
**All Command-Line Options**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-C] [-N] [-T] [-b] [-B] [-p] [--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [--version] [port]
|
||||
usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
|
||||
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
|
||||
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
|
||||
[--trust-key hexbytes] [--version] [port]
|
||||
|
||||
RNode Configuration and firmware utility. This program allows you to change various settings and startup modes of RNode. It can also install, flash and update the firmware on supported devices.
|
||||
RNode Configuration and firmware utility. This program allows you to change various
|
||||
settings and startup modes of RNode. It can also install, flash and update the firmware
|
||||
on supported devices.
|
||||
|
||||
positional arguments:
|
||||
port serial port where RNode is attached
|
||||
@@ -453,11 +709,14 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
-e, --extract Extract firmware from connected RNode for later use
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or update
|
||||
-C, --clear-cache Clear locally cached firmware files
|
||||
--baud-flash baud_flash
|
||||
Set specific baud rate when flashing device. Default is 921600
|
||||
-N, --normal Switch device to normal mode
|
||||
-T, --tnc Switch device to TNC mode
|
||||
-b, --bluetooth-on Turn device bluetooth on
|
||||
-B, --bluetooth-off Turn device bluetooth off
|
||||
-p, --bluetooth-pair Put device into bluetooth pairing mode
|
||||
-D i, --display i Set display intensity (0-255)
|
||||
--freq Hz Frequency in Hz for TNC mode
|
||||
--bw Hz Bandwidth in Hz for TNC mode
|
||||
--txp dBm TX power in dBm for TNC mode
|
||||
@@ -466,6 +725,8 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
--eeprom-backup Backup EEPROM to file
|
||||
--eeprom-dump Dump EEPROM to console
|
||||
--eeprom-wipe Unlock and wipe EEPROM
|
||||
-P, --public Display public part of signing key
|
||||
--trust-key hexbytes Public key to trust for device verification
|
||||
--version Print program version and exit
|
||||
|
||||
For more information on how to create your own RNodes, please read the :ref:`Creating RNodes<rnode-creating>`
|
||||
@@ -519,6 +780,9 @@ Reticulum as a System Service
|
||||
Instead of starting Reticulum manually, you can install ``rnsd`` as a system
|
||||
service and have it start automatically at boot.
|
||||
|
||||
Systemwide Service
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you installed Reticulum with ``pip``, the ``rnsd`` program will most likely
|
||||
be located in a user-local installation path only, which means ``systemd`` will not
|
||||
be able to execute it. In this case, you can simply symlink the ``rnsd`` program
|
||||
@@ -566,3 +830,48 @@ If you want to automatically start ``rnsd`` at boot, run:
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl enable rnsd
|
||||
|
||||
Userspace Service
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Alternatively you can use a user systemd service instead of a system wide one. This way the whole setup can be done as a regular user.
|
||||
Create a user systemd service files ``~/.config/systemd/user/rnsd.service`` with the following content:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[Unit]
|
||||
Description=Reticulum Network Stack Daemon
|
||||
After=default.target
|
||||
|
||||
[Service]
|
||||
# If you run Reticulum on WiFi devices,
|
||||
# or other devices that need some extra
|
||||
# time to initialise, you might want to
|
||||
# add a short delay before Reticulum is
|
||||
# started by systemd:
|
||||
# ExecStartPre=/bin/sleep 10
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ExecStart=RNS_BIN_DIR/rnsd --service
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
||||
Replace ``RNS_BIN_DIR`` with the path to your Reticulum binary directory (eg. /home/USERNAMEHERE/rns/bin).
|
||||
|
||||
Start user service:
|
||||
|
||||
.. code:: text
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user start rnsd.service
|
||||
|
||||
If you want to automatically start ``rnsd`` without having to log in as the USERNAMEHERE, do:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo loginctl enable-linger USERNAMEHERE
|
||||
systemctl --user enable rnsd.service
|
||||
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ networks, without any need for hierarchical or beaureucratic structures to contr
|
||||
or manage them, while ensuring individuals and communities full sovereignty
|
||||
over their own network segments.
|
||||
|
||||
Reticulum is a complete networking stack, and does not need IP or higher
|
||||
Reticulum is a **complete networking stack**, and does not need IP or higher
|
||||
layers, although it is easy to utilise IP (with TCP or UDP) as the underlying
|
||||
carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the
|
||||
Internet or private IP networks. Reticulum is built directly on cryptographic
|
||||
principles, allowing resilience and stable functionality in open and trustless
|
||||
networks.
|
||||
|
||||
No kernel modules or drivers are required. Reticulum runs completely in
|
||||
userland, and can run on practically any system that runs Python 3. Reticulum
|
||||
No kernel modules or drivers are required. Reticulum can run completely in
|
||||
userland, and will run on practically any system that runs Python 3. Reticulum
|
||||
runs well even on small single-board computers like the Pi Zero.
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ Current Status
|
||||
**Please know!** Reticulum should currently be considered beta software. All core protocol
|
||||
features are implemented and functioning, but additions will probably occur as
|
||||
real-world use is explored. *There will be bugs*. The API and wire-format can be
|
||||
considered stable at the moment, but could change if absolutely warranted.
|
||||
considered complete and stable at the moment, but could change if absolutely warranted.
|
||||
|
||||
|
||||
What does Reticulum Offer?
|
||||
@@ -71,7 +71,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Efficient link establishment
|
||||
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 297 bytes
|
||||
* Total cost of setting up an encrypted and verified link is only 3 packets, totalling 297 bytes
|
||||
|
||||
* Low cost of keeping links open at only 0.44 bits per second
|
||||
|
||||
@@ -91,7 +91,7 @@ What does Reticulum Offer?
|
||||
Where can Reticulum be Used?
|
||||
============================
|
||||
Over practically any medium that can support at least a half-duplex channel
|
||||
with 500 bits per second throughput, and an MTU of 500 bytes. Data radios,
|
||||
with greater throughput than 5 bits per second, and an MTU of 500 bytes. Data radios,
|
||||
modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes,
|
||||
ad-hoc WiFi, free-space optical links and similar systems are all examples
|
||||
of the types of interfaces Reticulum was designed for.
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* _sphinx_javascript_frameworks_compat.js
|
||||
* ~~~~~~~~~~
|
||||
*
|
||||
* Compatability shim for jQuery and underscores.js.
|
||||
*
|
||||
* WILL BE REMOVED IN Sphinx 6.0
|
||||
* xref RemovedInSphinx60Warning
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* select a different prefix for underscore
|
||||
*/
|
||||
$u = _.noConflict();
|
||||
|
||||
|
||||
/**
|
||||
* small helper function to urldecode strings
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
|
||||
*/
|
||||
jQuery.urldecode = function(x) {
|
||||
if (!x) {
|
||||
return x
|
||||
}
|
||||
return decodeURIComponent(x.replace(/\+/g, ' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* small helper function to urlencode strings
|
||||
*/
|
||||
jQuery.urlencode = encodeURIComponent;
|
||||
|
||||
/**
|
||||
* This function returns the parsed url parameters of the
|
||||
* current request. Multiple values per key are supported,
|
||||
* it will always return arrays of strings for the value parts.
|
||||
*/
|
||||
jQuery.getQueryParameters = function(s) {
|
||||
if (typeof s === 'undefined')
|
||||
s = document.location.search;
|
||||
var parts = s.substr(s.indexOf('?') + 1).split('&');
|
||||
var result = {};
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var tmp = parts[i].split('=', 2);
|
||||
var key = jQuery.urldecode(tmp[0]);
|
||||
var value = jQuery.urldecode(tmp[1]);
|
||||
if (key in result)
|
||||
result[key].push(value);
|
||||
else
|
||||
result[key] = [value];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* highlight a given string on a jquery object by wrapping it in
|
||||
* span elements with the given class name.
|
||||
*/
|
||||
jQuery.fn.highlightText = function(text, className) {
|
||||
function highlight(node, addItems) {
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
var pos = val.toLowerCase().indexOf(text);
|
||||
if (pos >= 0 &&
|
||||
!jQuery(node.parentNode).hasClass(className) &&
|
||||
!jQuery(node.parentNode).hasClass("nohighlight")) {
|
||||
var span;
|
||||
var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
|
||||
if (isInSVG) {
|
||||
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
||||
} else {
|
||||
span = document.createElement("span");
|
||||
span.className = className;
|
||||
}
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
|
||||
document.createTextNode(val.substr(pos + text.length)),
|
||||
node.nextSibling));
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
if (isInSVG) {
|
||||
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
||||
var bbox = node.parentElement.getBBox();
|
||||
rect.x.baseVal.value = bbox.x;
|
||||
rect.y.baseVal.value = bbox.y;
|
||||
rect.width.baseVal.value = bbox.width;
|
||||
rect.height.baseVal.value = bbox.height;
|
||||
rect.setAttribute('class', className);
|
||||
addItems.push({
|
||||
"parent": node.parentNode,
|
||||
"target": rect});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!jQuery(node).is("button, select, textarea")) {
|
||||
jQuery.each(node.childNodes, function() {
|
||||
highlight(this, addItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
var addItems = [];
|
||||
var result = this.each(function() {
|
||||
highlight(this, addItems);
|
||||
});
|
||||
for (var i = 0; i < addItems.length; ++i) {
|
||||
jQuery(addItems[i].parent).before(addItems[i].target);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/*
|
||||
* backward compatibility for jQuery.browser
|
||||
* This will be supported until firefox bug is fixed.
|
||||
*/
|
||||
if (!jQuery.browser) {
|
||||
jQuery.uaMatch = function(ua) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(msie) ([\w.]+)/.exec(ua) ||
|
||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
browser: match[ 1 ] || "",
|
||||
version: match[ 2 ] || "0"
|
||||
};
|
||||
};
|
||||
jQuery.browser = {};
|
||||
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
@@ -236,16 +236,6 @@ div.body p, div.body dd, div.body li, div.body blockquote {
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
a.brackets:before,
|
||||
span.brackets > a:before{
|
||||
content: "[";
|
||||
}
|
||||
|
||||
a.brackets:after,
|
||||
span.brackets > a:after {
|
||||
content: "]";
|
||||
}
|
||||
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
@@ -334,11 +324,17 @@ aside.sidebar {
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav.contents,
|
||||
aside.topic,
|
||||
div.admonition, div.topic, blockquote {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
nav.contents,
|
||||
aside.topic,
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px;
|
||||
@@ -377,6 +373,8 @@ div.body p.centered {
|
||||
|
||||
div.sidebar > :last-child,
|
||||
aside.sidebar > :last-child,
|
||||
nav.contents > :last-child,
|
||||
aside.topic > :last-child,
|
||||
div.topic > :last-child,
|
||||
div.admonition > :last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -384,6 +382,8 @@ div.admonition > :last-child {
|
||||
|
||||
div.sidebar::after,
|
||||
aside.sidebar::after,
|
||||
nav.contents::after,
|
||||
aside.topic::after,
|
||||
div.topic::after,
|
||||
div.admonition::after,
|
||||
blockquote::after {
|
||||
@@ -608,19 +608,27 @@ ol.simple p,
|
||||
ul.simple p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
dl.footnote > dt,
|
||||
dl.citation > dt {
|
||||
float: left;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
dl.footnote > dd,
|
||||
dl.citation > dd {
|
||||
aside.footnote > span,
|
||||
div.citation > span {
|
||||
float: left;
|
||||
}
|
||||
aside.footnote > span:last-of-type,
|
||||
div.citation > span:last-of-type {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
aside.footnote > p {
|
||||
margin-left: 2em;
|
||||
}
|
||||
div.citation > p {
|
||||
margin-left: 4em;
|
||||
}
|
||||
aside.footnote > p:last-of-type,
|
||||
div.citation > p:last-of-type {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
dl.footnote > dd:after,
|
||||
dl.citation > dd:after {
|
||||
aside.footnote > p:last-of-type:after,
|
||||
div.citation > p:last-of-type:after {
|
||||
content: "";
|
||||
clear: both;
|
||||
}
|
||||
@@ -636,10 +644,6 @@ dl.field-list > dt {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 5px;
|
||||
}
|
||||
dl.field-list > dt:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
|
||||
dl.field-list > dd {
|
||||
padding-left: 0.5em;
|
||||
@@ -666,6 +670,16 @@ dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.sig dd {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.sig dl {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
dl > dd:last-child,
|
||||
dl > dd:last-child > :last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -734,6 +748,14 @@ abbr, acronym {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.translated {
|
||||
background-color: rgba(207, 255, 207, 0.2)
|
||||
}
|
||||
|
||||
.untranslated {
|
||||
background-color: rgba(255, 207, 207, 0.2)
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
|
||||
@@ -35,7 +35,8 @@ div.highlight {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highlight:hover button.copybtn {
|
||||
/* Show the copybutton */
|
||||
.highlight:hover button.copybtn, button.copybtn.success {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const messages = {
|
||||
},
|
||||
'fr' : {
|
||||
'copy': 'Copier',
|
||||
'copy_to_clipboard': 'Copié dans le presse-papier',
|
||||
'copy_to_clipboard': 'Copier dans le presse-papier',
|
||||
'copy_success': 'Copié !',
|
||||
'copy_failure': 'Échec de la copie',
|
||||
},
|
||||
@@ -102,18 +102,25 @@ const clearSelection = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Changes tooltip text for two seconds, then changes it back
|
||||
// Changes tooltip text for a moment, then changes it back
|
||||
// We want the timeout of our `success` class to be a bit shorter than the
|
||||
// tooltip and icon change, so that we can hide the icon before changing back.
|
||||
var timeoutIcon = 2000;
|
||||
var timeoutSuccessClass = 1500;
|
||||
|
||||
const temporarilyChangeTooltip = (el, oldText, newText) => {
|
||||
el.setAttribute('data-tooltip', newText)
|
||||
el.classList.add('success')
|
||||
setTimeout(() => el.setAttribute('data-tooltip', oldText), 2000)
|
||||
setTimeout(() => el.classList.remove('success'), 2000)
|
||||
// Remove success a little bit sooner than we change the tooltip
|
||||
// So that we can use CSS to hide the copybutton first
|
||||
setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
|
||||
setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
|
||||
}
|
||||
|
||||
// Changes the copy button icon for two seconds, then changes it back
|
||||
const temporarilyChangeIcon = (el) => {
|
||||
el.innerHTML = iconCheck;
|
||||
setTimeout(() => {el.innerHTML = iconCopy}, 2000)
|
||||
setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
|
||||
}
|
||||
|
||||
const addCopyButtonToCodeCells = () => {
|
||||
@@ -125,7 +132,8 @@ const addCopyButtonToCodeCells = () => {
|
||||
}
|
||||
|
||||
// Add copybuttons to all of our code cells
|
||||
const codeCells = document.querySelectorAll('div.highlight pre')
|
||||
const COPYBUTTON_SELECTOR = 'div.highlight pre';
|
||||
const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
|
||||
codeCells.forEach((codeCell, index) => {
|
||||
const id = codeCellId(index)
|
||||
codeCell.setAttribute('id', id)
|
||||
@@ -141,10 +149,25 @@ function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes excluded text from a Node.
|
||||
*
|
||||
* @param {Node} target Node to filter.
|
||||
* @param {string} exclude CSS selector of nodes to exclude.
|
||||
* @returns {DOMString} Text from `target` with text removed.
|
||||
*/
|
||||
function filterText(target, exclude) {
|
||||
const clone = target.cloneNode(true); // clone as to not modify the live DOM
|
||||
if (exclude) {
|
||||
// remove excluded nodes
|
||||
clone.querySelectorAll(exclude).forEach(node => node.remove());
|
||||
}
|
||||
return clone.innerText;
|
||||
}
|
||||
|
||||
// Callback when a copy button is clicked. Will be passed the node that was clicked
|
||||
// should then grab the text and replace pieces of text that shouldn't be used in output
|
||||
function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
|
||||
|
||||
var regexp;
|
||||
var match;
|
||||
|
||||
@@ -199,7 +222,12 @@ function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onl
|
||||
|
||||
var copyTargetText = (trigger) => {
|
||||
var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
|
||||
return formatCopyText(target.innerText, '', false, true, true, true, '', '')
|
||||
|
||||
// get filtered text
|
||||
let exclude = '.linenos';
|
||||
|
||||
let text = filterText(target, exclude);
|
||||
return formatCopyText(text, '', false, true, true, true, '', '')
|
||||
}
|
||||
|
||||
// Initialize with a callback so we can modify the text before copy
|
||||
|
||||
@@ -2,10 +2,25 @@ function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes excluded text from a Node.
|
||||
*
|
||||
* @param {Node} target Node to filter.
|
||||
* @param {string} exclude CSS selector of nodes to exclude.
|
||||
* @returns {DOMString} Text from `target` with text removed.
|
||||
*/
|
||||
export function filterText(target, exclude) {
|
||||
const clone = target.cloneNode(true); // clone as to not modify the live DOM
|
||||
if (exclude) {
|
||||
// remove excluded nodes
|
||||
clone.querySelectorAll(exclude).forEach(node => node.remove());
|
||||
}
|
||||
return clone.innerText;
|
||||
}
|
||||
|
||||
// Callback when a copy button is clicked. Will be passed the node that was clicked
|
||||
// should then grab the text and replace pieces of text that shouldn't be used in output
|
||||
export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
|
||||
|
||||
var regexp;
|
||||
var match;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Base JavaScript utilities for all Sphinx HTML documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.4.8 beta',
|
||||
VERSION: '0.6.8 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
Vendored
-10881
File diff suppressed because it is too large
Load Diff
Vendored
-2
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
* This script contains the language-specific data used by searchtools.js,
|
||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
||||
*
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #a40000 } /* Generic.Deleted */
|
||||
.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */
|
||||
.highlight .ges { color: #000000; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
||||
.highlight .gr { color: #ef2929 } /* Generic.Error */
|
||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
||||
@@ -101,12 +102,13 @@ body[data-theme="dark"] .highlight .x { color: #d0d0d0 } /* Other */
|
||||
body[data-theme="dark"] .highlight .p { color: #d0d0d0 } /* Punctuation */
|
||||
body[data-theme="dark"] .highlight .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */
|
||||
body[data-theme="dark"] .highlight .cm { color: #ababab; font-style: italic } /* Comment.Multiline */
|
||||
body[data-theme="dark"] .highlight .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */
|
||||
body[data-theme="dark"] .highlight .cp { color: #ff3a3a; font-weight: bold } /* Comment.Preproc */
|
||||
body[data-theme="dark"] .highlight .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */
|
||||
body[data-theme="dark"] .highlight .c1 { color: #ababab; font-style: italic } /* Comment.Single */
|
||||
body[data-theme="dark"] .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */
|
||||
body[data-theme="dark"] .highlight .gd { color: #d22323 } /* Generic.Deleted */
|
||||
body[data-theme="dark"] .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */
|
||||
body[data-theme="dark"] .highlight .ges { color: #d0d0d0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
||||
body[data-theme="dark"] .highlight .gr { color: #d22323 } /* Generic.Error */
|
||||
body[data-theme="dark"] .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */
|
||||
body[data-theme="dark"] .highlight .gi { color: #589819 } /* Generic.Inserted */
|
||||
@@ -186,12 +188,13 @@ body:not([data-theme="light"]) .highlight .x { color: #d0d0d0 } /* Other */
|
||||
body:not([data-theme="light"]) .highlight .p { color: #d0d0d0 } /* Punctuation */
|
||||
body:not([data-theme="light"]) .highlight .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */
|
||||
body:not([data-theme="light"]) .highlight .cm { color: #ababab; font-style: italic } /* Comment.Multiline */
|
||||
body:not([data-theme="light"]) .highlight .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */
|
||||
body:not([data-theme="light"]) .highlight .cp { color: #ff3a3a; font-weight: bold } /* Comment.Preproc */
|
||||
body:not([data-theme="light"]) .highlight .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */
|
||||
body:not([data-theme="light"]) .highlight .c1 { color: #ababab; font-style: italic } /* Comment.Single */
|
||||
body:not([data-theme="light"]) .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */
|
||||
body:not([data-theme="light"]) .highlight .gd { color: #d22323 } /* Generic.Deleted */
|
||||
body:not([data-theme="light"]) .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */
|
||||
body:not([data-theme="light"]) .highlight .ges { color: #d0d0d0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
||||
body:not([data-theme="light"]) .highlight .gr { color: #d22323 } /* Generic.Error */
|
||||
body:not([data-theme="light"]) .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */
|
||||
body:not([data-theme="light"]) .highlight .gi { color: #589819 } /* Generic.Inserted */
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/*!
|
||||
* gumshoejs v5.1.2 (patched by @pradyunsg)
|
||||
* A simple, framework-agnostic scrollspy script.
|
||||
* (c) 2019 Chris Ferdinandi
|
||||
* MIT License
|
||||
* http://github.com/cferdinandi/gumshoe
|
||||
*/
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Sphinx JavaScript utilities for the full-text search.
|
||||
*
|
||||
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
|
||||
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+759
-31
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -258,7 +258,7 @@ program.</p>
|
||||
<span class="c1"># Destinations are endpoints in Reticulum, that can be addressed</span>
|
||||
<span class="c1"># and communicated with. Destinations can also announce their</span>
|
||||
<span class="c1"># existence, which will let the network know they are reachable</span>
|
||||
<span class="c1"># and autoomatically create paths to them, from anywhere else</span>
|
||||
<span class="c1"># and automatically create paths to them, from anywhere else</span>
|
||||
<span class="c1"># in the network.</span>
|
||||
<span class="n">destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">identity</span><span class="p">,</span>
|
||||
@@ -269,7 +269,7 @@ program.</p>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure the destination to automatically prove all</span>
|
||||
<span class="c1"># packets adressed to it. By doing this, RNS will automatically</span>
|
||||
<span class="c1"># packets addressed to it. By doing this, RNS will automatically</span>
|
||||
<span class="c1"># generate a proof for each incoming packet and transmit it</span>
|
||||
<span class="c1"># back to the sender of that packet. This will let anyone that</span>
|
||||
<span class="c1"># tries to communicate with the destination know whether their</span>
|
||||
@@ -375,7 +375,7 @@ notifications about announces from relevant destinations.</p>
|
||||
<span class="c1"># Destinations are endpoints in Reticulum, that can be addressed</span>
|
||||
<span class="c1"># and communicated with. Destinations can also announce their</span>
|
||||
<span class="c1"># existence, which will let the network know they are reachable</span>
|
||||
<span class="c1"># and autoomatically create paths to them, from anywhere else</span>
|
||||
<span class="c1"># and automatically create paths to them, from anywhere else</span>
|
||||
<span class="c1"># in the network.</span>
|
||||
<span class="n">destination_1</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">identity</span><span class="p">,</span>
|
||||
@@ -396,7 +396,7 @@ notifications about announces from relevant destinations.</p>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure the destinations to automatically prove all</span>
|
||||
<span class="c1"># packets adressed to it. By doing this, RNS will automatically</span>
|
||||
<span class="c1"># packets addressed to it. By doing this, RNS will automatically</span>
|
||||
<span class="c1"># generate a proof for each incoming packet and transmit it</span>
|
||||
<span class="c1"># back to the sender of that packet. This will let anyone that</span>
|
||||
<span class="c1"># tries to communicate with the destination know whether their</span>
|
||||
@@ -697,7 +697,7 @@ the Packet interface.</p>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure the destination to automatically prove all</span>
|
||||
<span class="c1"># packets adressed to it. By doing this, RNS will automatically</span>
|
||||
<span class="c1"># packets addressed to it. By doing this, RNS will automatically</span>
|
||||
<span class="c1"># generate a proof for each incoming packet and transmit it</span>
|
||||
<span class="c1"># back to the sender of that packet.</span>
|
||||
<span class="n">echo_destination</span><span class="o">.</span><span class="n">set_proof_strategy</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">PROVE_ALL</span><span class="p">)</span>
|
||||
@@ -861,6 +861,7 @@ the Packet interface.</p>
|
||||
<span class="c1"># If we do not know this destination, tell the</span>
|
||||
<span class="c1"># user to wait for an announce to arrive.</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Destination is not yet known. Requesting path..."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Hit enter to manually retry once an announce is received."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># This function is called when our reply destination</span>
|
||||
@@ -1632,8 +1633,8 @@ the link has been established.</p>
|
||||
<span class="c1"># A reference to the latest client link that connected</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">random_text_generator</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">remote_identity</span><span class="p">,</span> <span class="n">requested_at</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Generating response to request "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_id</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">random_text_generator</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">link_id</span><span class="p">,</span> <span class="n">remote_identity</span><span class="p">,</span> <span class="n">requested_at</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Generating response to request "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span><span class="o">+</span><span class="s2">" on link "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">link_id</span><span class="p">))</span>
|
||||
<span class="n">texts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"They looked up"</span><span class="p">,</span> <span class="s2">"On each full moon"</span><span class="p">,</span> <span class="s2">"Becky was upset"</span><span class="p">,</span> <span class="s2">"I’ll stay away from it"</span><span class="p">,</span> <span class="s2">"The pet shop stocks everything"</span><span class="p">]</span>
|
||||
<span class="k">return</span> <span class="n">texts</span><span class="p">[</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">texts</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)]</span>
|
||||
|
||||
@@ -1898,6 +1899,735 @@ the link has been established.</p>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py</a>.</p>
|
||||
</section>
|
||||
<section id="channel">
|
||||
<span id="example-channel"></span><h2>Channel<a class="headerlink" href="#channel" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Channel</em> example explores using a <code class="docutils literal notranslate"><span class="pre">Channel</span></code> to send structured
|
||||
data between peers of a <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
|
||||
<span class="c1"># This RNS example demonstrates how to set up a link to #</span>
|
||||
<span class="c1"># a destination, and pass structured messages over it #</span>
|
||||
<span class="c1"># using a channel. #</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">os</span>
|
||||
<span class="kn">import</span> <span class="nn">sys</span>
|
||||
<span class="kn">import</span> <span class="nn">time</span>
|
||||
<span class="kn">import</span> <span class="nn">argparse</span>
|
||||
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">RNS</span>
|
||||
<span class="kn">from</span> <span class="nn">RNS.vendor</span> <span class="kn">import</span> <span class="n">umsgpack</span>
|
||||
|
||||
<span class="c1"># Let's define an app name. We'll use this for all</span>
|
||||
<span class="c1"># destinations we create. Since this echo example</span>
|
||||
<span class="c1"># is part of a range of example utilities, we'll put</span>
|
||||
<span class="c1"># them all within the app namespace "example_utilities"</span>
|
||||
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">"example_utilities"</span>
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Shared Objects ######################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># Channel data must be structured in a subclass of</span>
|
||||
<span class="c1"># MessageBase. This ensures that the channel will be able</span>
|
||||
<span class="c1"># to serialize and deserialize the object and multiplex it</span>
|
||||
<span class="c1"># with other objects. Both ends of a link will need the</span>
|
||||
<span class="c1"># same object definitions to be able to communicate over</span>
|
||||
<span class="c1"># a channel.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># Note: The objects we wish to use over the channel must</span>
|
||||
<span class="c1"># be registered with the channel, and each link has a</span>
|
||||
<span class="c1"># different channel instance. See the client_connected</span>
|
||||
<span class="c1"># and link_established functions in this example to see</span>
|
||||
<span class="c1"># how message types are registered.</span>
|
||||
|
||||
<span class="c1"># Let's make a simple message class called StringMessage</span>
|
||||
<span class="c1"># that will convey a string with a timestamp.</span>
|
||||
|
||||
<span class="k">class</span> <span class="nc">StringMessage</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">MessageBase</span><span class="p">):</span>
|
||||
<span class="c1"># The MSGTYPE class variable needs to be assigned a</span>
|
||||
<span class="c1"># 2 byte integer value. This identifier allows the</span>
|
||||
<span class="c1"># channel to look up your message's constructor when a</span>
|
||||
<span class="c1"># message arrives over the channel.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># MSGTYPE must be unique across all message types we</span>
|
||||
<span class="c1"># register with the channel. MSGTYPEs >= 0xf000 are</span>
|
||||
<span class="c1"># reserved for the system.</span>
|
||||
<span class="n">MSGTYPE</span> <span class="o">=</span> <span class="mh">0x0101</span>
|
||||
|
||||
<span class="c1"># The constructor of our object must be callable with</span>
|
||||
<span class="c1"># no arguments. We can have parameters, but they must</span>
|
||||
<span class="c1"># have a default assignment.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># This is needed so the channel can create an empty</span>
|
||||
<span class="c1"># version of our message into which the incoming</span>
|
||||
<span class="c1"># message can be unpacked.</span>
|
||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">data</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Finally, our message needs to implement functions</span>
|
||||
<span class="c1"># the channel can call to pack and unpack our message</span>
|
||||
<span class="c1"># to/from the raw packet payload. We'll use the</span>
|
||||
<span class="c1"># umsgpack package bundled with RNS. We could also use</span>
|
||||
<span class="c1"># the struct package bundled with Python if we wanted</span>
|
||||
<span class="c1"># more control over the structure of the packed bytes.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># Also note that packed message objects must fit</span>
|
||||
<span class="c1"># entirely in one packet. The number of bytes</span>
|
||||
<span class="c1"># available for message payloads can be queried from</span>
|
||||
<span class="c1"># the channel using the Channel.MDU property. The</span>
|
||||
<span class="c1"># channel MDU is slightly less than the link MDU due</span>
|
||||
<span class="c1"># to encoding the message header.</span>
|
||||
|
||||
<span class="c1"># The pack function encodes the message contents into</span>
|
||||
<span class="c1"># a byte stream.</span>
|
||||
<span class="k">def</span> <span class="nf">pack</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bytes</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">packb</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># And the unpack function decodes a byte stream into</span>
|
||||
<span class="c1"># the message contents.</span>
|
||||
<span class="k">def</span> <span class="nf">unpack</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">raw</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">unpackb</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Server Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the latest client link that connected</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a server</span>
|
||||
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Randomly create a new identity for our link example</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We create a destination that clients can connect to. We</span>
|
||||
<span class="c1"># want clients to create links to this destination, so we</span>
|
||||
<span class="c1"># need to create a "single" destination type.</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"channelexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure a function that will get called every time</span>
|
||||
<span class="c1"># a new client creates a link to this destination.</span>
|
||||
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything's ready!</span>
|
||||
<span class="c1"># Let's Wait for client requests or user input</span>
|
||||
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
|
||||
<span class="c1"># Let the user know that everything is ready</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Link example "</span><span class="o">+</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
|
||||
<span class="s2">" running, waiting for a connection."</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Hit enter to manually send an announce (Ctrl-C to quit)"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We enter a loop that runs until the users exits.</span>
|
||||
<span class="c1"># If the user hits enter, we will announce our server</span>
|
||||
<span class="c1"># destination on the network, which will let clients</span>
|
||||
<span class="c1"># know how to create messages directed towards it.</span>
|
||||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||||
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Sent announce from "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># When a client establishes a link to our server</span>
|
||||
<span class="c1"># destination, this function will be called with</span>
|
||||
<span class="c1"># a reference to the link.</span>
|
||||
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">latest_client_link</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client connected"</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Register message types and add callback to channel</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">register_message_type</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">add_message_handler</span><span class="p">(</span><span class="n">server_message_received</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client disconnected"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_message_received</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> A message handler</span>
|
||||
<span class="sd"> @param message: An instance of a subclass of MessageBase</span>
|
||||
<span class="sd"> @return: True if message was handled</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">global</span> <span class="n">latest_client_link</span>
|
||||
<span class="c1"># When a message is received over any active link,</span>
|
||||
<span class="c1"># the replies will all be directed to the last client</span>
|
||||
<span class="c1"># that connected.</span>
|
||||
|
||||
<span class="c1"># In a message handler, any deserializable message</span>
|
||||
<span class="c1"># that arrives over the link's channel will be passed</span>
|
||||
<span class="c1"># to all message handlers, unless a preceding handler indicates it</span>
|
||||
<span class="c1"># has handled the message.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringMessage</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data on the link: "</span> <span class="o">+</span> <span class="n">message</span><span class="o">.</span><span class="n">data</span> <span class="o">+</span> <span class="s2">" (message created at "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">+</span> <span class="s2">")"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">reply_message</span> <span class="o">=</span> <span class="n">StringMessage</span><span class="p">(</span><span class="s2">"I received </span><span class="se">\"</span><span class="s2">"</span><span class="o">+</span><span class="n">message</span><span class="o">.</span><span class="n">data</span><span class="o">+</span><span class="s2">"</span><span class="se">\"</span><span class="s2"> over the link"</span><span class="p">)</span>
|
||||
<span class="n">latest_client_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">reply_message</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Incoming messages are sent to each message</span>
|
||||
<span class="c1"># handler added to the channel, in the order they</span>
|
||||
<span class="c1"># were added.</span>
|
||||
<span class="c1"># If any message handler returns True, the message</span>
|
||||
<span class="c1"># is considered handled and any subsequent</span>
|
||||
<span class="c1"># handlers are skipped.</span>
|
||||
<span class="k">return</span> <span class="kc">True</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Client Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the server link</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a client</span>
|
||||
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We need a binary representation of the destination</span>
|
||||
<span class="c1"># hash that was entered on the command line</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||||
<span class="s2">"Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes)."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
|
||||
<span class="k">except</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Check if we know a path to the destination</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Destination is not yet known. Requesting path and waiting for announce to arrive..."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Recall the server identity</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that we'll begin connecting</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Establishing link with server..."</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When the server identity is known, we set</span>
|
||||
<span class="c1"># up a destination</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"channelexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># And create a link</span>
|
||||
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We'll also set up functions to inform the</span>
|
||||
<span class="c1"># user when the link is established or closed</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything is set up, so let's enter a loop</span>
|
||||
<span class="c1"># for the user to interact with the example</span>
|
||||
<span class="n">client_loop</span><span class="p">()</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span>
|
||||
|
||||
<span class="c1"># Wait for the link to become active</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Check if we should quit the example</span>
|
||||
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"quit"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"q"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"exit"</span><span class="p">:</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># If not, send the entered text over the link</span>
|
||||
<span class="k">if</span> <span class="n">text</span> <span class="o">!=</span> <span class="s2">""</span><span class="p">:</span>
|
||||
<span class="n">message</span> <span class="o">=</span> <span class="n">StringMessage</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
|
||||
<span class="n">packed_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">pack</span><span class="p">())</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">server_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="n">channel</span><span class="o">.</span><span class="n">is_ready_to_send</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">packed_size</span> <span class="o"><=</span> <span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">:</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Cannot send this packet, the data size of "</span><span class="o">+</span>
|
||||
<span class="nb">str</span><span class="p">(</span><span class="n">packed_size</span><span class="p">)</span><span class="o">+</span><span class="s2">" bytes exceeds the link packet MDU of "</span><span class="o">+</span>
|
||||
<span class="nb">str</span><span class="p">(</span><span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">)</span><span class="o">+</span><span class="s2">" bytes"</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Channel is not ready to send, please wait for "</span> <span class="o">+</span>
|
||||
<span class="s2">"pending messages to complete."</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Error while sending data over the link: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># This function is called when a link</span>
|
||||
<span class="c1"># has been established with the server</span>
|
||||
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="c1"># We store a reference to the link</span>
|
||||
<span class="c1"># instance for later use</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="c1"># Register messages and add handler to channel</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">register_message_type</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">add_message_handler</span><span class="p">(</span><span class="n">client_message_received</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that the server is</span>
|
||||
<span class="c1"># connected</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link established with server, enter some text to send, or </span><span class="se">\"</span><span class="s2">quit</span><span class="se">\"</span><span class="s2"> to quit"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When a link is closed, we'll inform the</span>
|
||||
<span class="c1"># user, and exit the program</span>
|
||||
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link timed out, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link was closed by the server, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link closed, exiting now"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When a packet is received over the channel, we</span>
|
||||
<span class="c1"># simply print out the data.</span>
|
||||
<span class="k">def</span> <span class="nf">client_message_received</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringMessage</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data on the link: "</span> <span class="o">+</span> <span class="n">message</span><span class="o">.</span><span class="n">data</span> <span class="o">+</span> <span class="s2">" (message created at "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">+</span> <span class="s2">")"</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Program Startup #####################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># This part of the program runs at startup,</span>
|
||||
<span class="c1"># and parses input of from the user, and then</span>
|
||||
<span class="c1"># starts up the desired program mode.</span>
|
||||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">"Simple channel example"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"-s"</span><span class="p">,</span>
|
||||
<span class="s2">"--server"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store_true"</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"wait for incoming link requests from clients"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"--config"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"path to alternative Reticulum config directory"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"destination"</span><span class="p">,</span>
|
||||
<span class="n">nargs</span><span class="o">=</span><span class="s2">"?"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"hexadecimal hash of the server destination"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
|
||||
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py</a>.</p>
|
||||
</section>
|
||||
<section id="buffer">
|
||||
<h2>Buffer<a class="headerlink" href="#buffer" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Buffer</em> example explores using buffered readers and writers to send
|
||||
binary data between peers of a <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
|
||||
<span class="c1"># This RNS example demonstrates how to set up a link to #</span>
|
||||
<span class="c1"># a destination, and pass binary data over it using a #</span>
|
||||
<span class="c1"># channel buffer. #</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
|
||||
<span class="kn">import</span> <span class="nn">os</span>
|
||||
<span class="kn">import</span> <span class="nn">sys</span>
|
||||
<span class="kn">import</span> <span class="nn">time</span>
|
||||
<span class="kn">import</span> <span class="nn">argparse</span>
|
||||
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">RNS</span>
|
||||
<span class="kn">from</span> <span class="nn">RNS.vendor</span> <span class="kn">import</span> <span class="n">umsgpack</span>
|
||||
|
||||
<span class="c1"># Let's define an app name. We'll use this for all</span>
|
||||
<span class="c1"># destinations we create. Since this echo example</span>
|
||||
<span class="c1"># is part of a range of example utilities, we'll put</span>
|
||||
<span class="c1"># them all within the app namespace "example_utilities"</span>
|
||||
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">"example_utilities"</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Server Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the latest client link that connected</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># A reference to the latest buffer object</span>
|
||||
<span class="n">latest_buffer</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a server</span>
|
||||
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Randomly create a new identity for our example</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We create a destination that clients can connect to. We</span>
|
||||
<span class="c1"># want clients to create links to this destination, so we</span>
|
||||
<span class="c1"># need to create a "single" destination type.</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"bufferexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure a function that will get called every time</span>
|
||||
<span class="c1"># a new client creates a link to this destination.</span>
|
||||
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything's ready!</span>
|
||||
<span class="c1"># Let's Wait for client requests or user input</span>
|
||||
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
|
||||
<span class="c1"># Let the user know that everything is ready</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Link buffer example "</span><span class="o">+</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
|
||||
<span class="s2">" running, waiting for a connection."</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Hit enter to manually send an announce (Ctrl-C to quit)"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We enter a loop that runs until the users exits.</span>
|
||||
<span class="c1"># If the user hits enter, we will announce our server</span>
|
||||
<span class="c1"># destination on the network, which will let clients</span>
|
||||
<span class="c1"># know how to create messages directed towards it.</span>
|
||||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||||
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Sent announce from "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># When a client establishes a link to our server</span>
|
||||
<span class="c1"># destination, this function will be called with</span>
|
||||
<span class="c1"># a reference to the link.</span>
|
||||
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">latest_client_link</span><span class="p">,</span> <span class="n">latest_buffer</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client connected"</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># If a new connection is received, the old reader</span>
|
||||
<span class="c1"># needs to be disconnected.</span>
|
||||
<span class="k">if</span> <span class="n">latest_buffer</span><span class="p">:</span>
|
||||
<span class="n">latest_buffer</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="c1"># Create buffer objects.</span>
|
||||
<span class="c1"># The stream_id parameter to these functions is</span>
|
||||
<span class="c1"># a bit like a file descriptor, except that it</span>
|
||||
<span class="c1"># is unique to the *receiver*.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># In this example, both the reader and the writer</span>
|
||||
<span class="c1"># use stream_id = 0, but there are actually two</span>
|
||||
<span class="c1"># separate unidirectional streams flowing in</span>
|
||||
<span class="c1"># opposite directions.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">latest_buffer</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Buffer</span><span class="o">.</span><span class="n">create_bidirectional_buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">channel</span><span class="p">,</span> <span class="n">server_buffer_ready</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client disconnected"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_buffer_ready</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Callback from buffer when buffer has data available</span>
|
||||
|
||||
<span class="sd"> :param ready_bytes: The number of bytes ready to read</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">global</span> <span class="n">latest_buffer</span>
|
||||
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">latest_buffer</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">)</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data over the buffer: "</span> <span class="o">+</span> <span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="n">reply_message</span> <span class="o">=</span> <span class="s2">"I received </span><span class="se">\"</span><span class="s2">"</span><span class="o">+</span><span class="n">data</span><span class="o">+</span><span class="s2">"</span><span class="se">\"</span><span class="s2"> over the buffer"</span>
|
||||
<span class="n">reply_message</span> <span class="o">=</span> <span class="n">reply_message</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
|
||||
<span class="n">latest_buffer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">reply_message</span><span class="p">)</span>
|
||||
<span class="n">latest_buffer</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Client Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the server link</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># A reference to the buffer object, needed to share the</span>
|
||||
<span class="c1"># object from the link connected callback to the client</span>
|
||||
<span class="c1"># loop.</span>
|
||||
<span class="n">buffer</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a client</span>
|
||||
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We need a binary representation of the destination</span>
|
||||
<span class="c1"># hash that was entered on the command line</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||||
<span class="s2">"Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes)."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
|
||||
<span class="k">except</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Check if we know a path to the destination</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Destination is not yet known. Requesting path and waiting for announce to arrive..."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Recall the server identity</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that we'll begin connecting</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Establishing link with server..."</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When the server identity is known, we set</span>
|
||||
<span class="c1"># up a destination</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"bufferexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># And create a link</span>
|
||||
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We'll also set up functions to inform the</span>
|
||||
<span class="c1"># user when the link is established or closed</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything is set up, so let's enter a loop</span>
|
||||
<span class="c1"># for the user to interact with the example</span>
|
||||
<span class="n">client_loop</span><span class="p">()</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span>
|
||||
|
||||
<span class="c1"># Wait for the link to become active</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Check if we should quit the example</span>
|
||||
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"quit"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"q"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"exit"</span><span class="p">:</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># Otherwise, encode the text and write it to the buffer.</span>
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
|
||||
<span class="n">buffer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
|
||||
<span class="c1"># Flush the buffer to force the data to be sent.</span>
|
||||
<span class="n">buffer</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Error while sending data over the link buffer: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># This function is called when a link</span>
|
||||
<span class="c1"># has been established with the server</span>
|
||||
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="c1"># We store a reference to the link</span>
|
||||
<span class="c1"># instance for later use</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span><span class="p">,</span> <span class="n">buffer</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="c1"># Create buffer, see server_client_connected() for</span>
|
||||
<span class="c1"># more detail about setting up the buffer.</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">buffer</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Buffer</span><span class="o">.</span><span class="n">create_bidirectional_buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">channel</span><span class="p">,</span> <span class="n">client_buffer_ready</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that the server is</span>
|
||||
<span class="c1"># connected</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link established with server, enter some text to send, or </span><span class="se">\"</span><span class="s2">quit</span><span class="se">\"</span><span class="s2"> to quit"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When a link is closed, we'll inform the</span>
|
||||
<span class="c1"># user, and exit the program</span>
|
||||
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link timed out, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link was closed by the server, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link closed, exiting now"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When the buffer has new data, read it and write it to the terminal.</span>
|
||||
<span class="k">def</span> <span class="nf">client_buffer_ready</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">buffer</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">buffer</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data over the link buffer: "</span> <span class="o">+</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Program Startup #####################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># This part of the program runs at startup,</span>
|
||||
<span class="c1"># and parses input of from the user, and then</span>
|
||||
<span class="c1"># starts up the desired program mode.</span>
|
||||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">"Simple buffer example"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"-s"</span><span class="p">,</span>
|
||||
<span class="s2">"--server"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store_true"</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"wait for incoming link requests from clients"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"--config"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"path to alternative Reticulum config directory"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"destination"</span><span class="p">,</span>
|
||||
<span class="n">nargs</span><span class="o">=</span><span class="s2">"?"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"hexadecimal hash of the server destination"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
|
||||
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py</a>.</p>
|
||||
</section>
|
||||
<section id="filetransfer">
|
||||
<span id="example-filetransfer"></span><h2>Filetransfer<a class="headerlink" href="#filetransfer" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Filetransfer</em> example implements a basic file-server program that
|
||||
@@ -2354,8 +3084,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<span class="c1"># And set up a small job to check for</span>
|
||||
<span class="c1"># a potential timeout in receiving the</span>
|
||||
<span class="c1"># file list</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="n">filelist_timeout_job</span><span class="p">)</span>
|
||||
<span class="n">thread</span><span class="o">.</span><span class="n">setDaemon</span><span class="p">(</span><span class="kc">True</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="n">filelist_timeout_job</span><span class="p">,</span> <span class="n">daemon</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># This job just sleeps for the specified</span>
|
||||
@@ -2543,7 +3272,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -2578,6 +3307,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<li><a class="reference internal" href="#link">Link</a></li>
|
||||
<li><a class="reference internal" href="#example-identify">Identification</a></li>
|
||||
<li><a class="reference internal" href="#requests-responses">Requests & Responses</a></li>
|
||||
<li><a class="reference internal" href="#channel">Channel</a></li>
|
||||
<li><a class="reference internal" href="#buffer">Buffer</a></li>
|
||||
<li><a class="reference internal" href="#filetransfer">Filetransfer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -2590,14 +3321,11 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-22
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -236,7 +236,7 @@
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -257,14 +257,11 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+114
-29
@@ -4,12 +4,12 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -159,16 +159,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -179,7 +179,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -222,18 +222,36 @@
|
||||
|
||||
<section class="genindex-section">
|
||||
<h1 id="index">Index</h1>
|
||||
<div class="genindex-jumpbox"><a href="#A"><strong>A</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <a href="#I"><strong>I</strong></a> | <a href="#K"><strong>K</strong></a> | <a href="#L"><strong>L</strong></a> | <a href="#M"><strong>M</strong></a> | <a href="#N"><strong>N</strong></a> | <a href="#P"><strong>P</strong></a> | <a href="#R"><strong>R</strong></a> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#V"><strong>V</strong></a></div>
|
||||
<div class="genindex-jumpbox"><a href="#_"><strong>_</strong></a> | <a href="#A"><strong>A</strong></a> | <a href="#B"><strong>B</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <a href="#I"><strong>I</strong></a> | <a href="#K"><strong>K</strong></a> | <a href="#L"><strong>L</strong></a> | <a href="#M"><strong>M</strong></a> | <a href="#N"><strong>N</strong></a> | <a href="#P"><strong>P</strong></a> | <a href="#R"><strong>R</strong></a> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#U"><strong>U</strong></a> | <a href="#V"><strong>V</strong></a></div>
|
||||
</section>
|
||||
<section id="_" class="genindex-section">
|
||||
<h2>_</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.RawChannelReader.__init__">__init__() (RNS.RawChannelReader method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.RawChannelWriter.__init__">(RNS.RawChannelWriter method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="A" class="genindex-section">
|
||||
<h2>A</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.accepts_links">accepts_links() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.add_message_handler">add_message_handler() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RawChannelReader.add_ready_callback">add_ready_callback() (RNS.RawChannelReader method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.announce">announce() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.ANNOUNCE_CAP">ANNOUNCE_CAP (RNS.Reticulum attribute)</a>
|
||||
@@ -244,17 +262,35 @@
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="B" class="genindex-section">
|
||||
<h2>B</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Buffer">Buffer (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="C" class="genindex-section">
|
||||
<h2>C</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Resource.cancel">cancel() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Channel.Channel">Channel (class in RNS.Channel)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.clear_default_app_data">clear_default_app_data() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer.create_bidirectional_buffer">create_bidirectional_buffer() (RNS.Buffer static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.create_keys">create_keys() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer.create_reader">create_reader() (RNS.Buffer static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer.create_writer">create_writer() (RNS.Buffer static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.CURVE">CURVE (RNS.Identity attribute)</a>
|
||||
|
||||
@@ -330,9 +366,15 @@
|
||||
<h2>G</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link.get_channel">get_channel() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.get_data_size">get_data_size() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_establishment_rate">get_establishment_rate() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.get_hash">get_hash() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.get_instance">get_instance() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.get_parts">get_parts() (RNS.Resource method)</a>
|
||||
</li>
|
||||
@@ -350,10 +392,12 @@
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.Identity.get_public_key">get_public_key() (RNS.Identity method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
|
||||
<li><a href="reference.html#RNS.Link.get_q">get_q() (RNS.Link method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_request_id">get_request_id() (RNS.RequestReceipt method)</a>
|
||||
@@ -361,10 +405,14 @@
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_response">get_response() (RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_rssi">get_rssi() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.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>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
|
||||
|
||||
@@ -409,6 +457,8 @@
|
||||
<li><a href="reference.html#RNS.Link.inactive_for">inactive_for() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.is_compressed">is_compressed() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.is_ready_to_send">is_ready_to_send() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -454,6 +504,16 @@
|
||||
<h2>M</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.MDU">MDU (RNS.Channel.Channel property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.MessageBase">MessageBase (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Reticulum.MINIMUM_BITRATE">MINIMUM_BITRATE (RNS.Reticulum attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.MessageBase.MSGTYPE">MSGTYPE (RNS.MessageBase attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.MTU">MTU (RNS.Reticulum attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
@@ -482,12 +542,14 @@
|
||||
<h2>P</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
|
||||
<li><a href="reference.html#RNS.MessageBase.pack">pack() (RNS.MessageBase method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
|
||||
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
|
||||
@@ -500,18 +562,28 @@
|
||||
<h2>R</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.RawChannelReader">RawChannelReader (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RawChannelWriter">RawChannelWriter (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.recall">recall() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.recall_app_data">recall_app_data() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.register_announce_handler">register_announce_handler() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.register_message_type">register_message_type() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
|
||||
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.remove_message_handler">remove_message_handler() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RawChannelReader.remove_ready_callback">remove_ready_callback() (RNS.RawChannelReader method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.request_path">request_path() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt">RequestReceipt (class in RNS)</a>
|
||||
@@ -530,8 +602,12 @@
|
||||
<h2>S</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet.send">send() (RNS.Packet method)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.send">send() (RNS.Channel.Channel method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Packet.send">(RNS.Packet method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.Destination.set_default_app_data">set_default_app_data() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.set_delivery_callback">set_delivery_callback() (RNS.PacketReceipt method)</a>
|
||||
@@ -590,10 +666,12 @@
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.to_file">to_file() (RNS.Identity method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport">Transport (class in RNS)</a>
|
||||
<li><a href="reference.html#RNS.Link.track_phy_stats">track_phy_stats() (RNS.Link method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Transport">Transport (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.transport_enabled">transport_enabled() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.truncated_hash">truncated_hash() (RNS.Identity static method)</a>
|
||||
@@ -604,6 +682,16 @@
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="U" class="genindex-section">
|
||||
<h2>U</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.MessageBase.unpack">unpack() (RNS.MessageBase method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="V" class="genindex-section">
|
||||
<h2>V</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
@@ -626,7 +714,7 @@
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -647,14 +735,11 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -229,18 +229,38 @@ scenarios.</p>
|
||||
<section id="standalone-reticulum-installation">
|
||||
<h2>Standalone Reticulum Installation<a class="headerlink" href="#standalone-reticulum-installation" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
the easiest way is via the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you no not already have pip installed, you can install it using the package manager
|
||||
<p>If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">apt</span> <span class="pre">install</span> <span class="pre">python3-pip</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.4.6</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar.</p>
|
||||
<p>You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.5.1</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="resolving-dependency-installation-issues">
|
||||
<h2>Resolving Dependency & Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h2>
|
||||
<p>On some platforms, there may not be binary packages available for all dependencies, and
|
||||
<code class="docutils literal notranslate"><span class="pre">pip</span></code> installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Debian / Ubuntu / Derivatives</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span>
|
||||
|
||||
<span class="c1"># Arch / Manjaro / Derivatives</span>
|
||||
<span class="n">sudo</span> <span class="n">pamac</span> <span class="n">install</span> <span class="n">base</span><span class="o">-</span><span class="n">devel</span>
|
||||
|
||||
<span class="c1"># Fedora</span>
|
||||
<span class="n">sudo</span> <span class="n">dnf</span> <span class="n">groupinstall</span> <span class="s2">"Development Tools"</span> <span class="s2">"Development Libraries"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>With the base development packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don’t have pre-
|
||||
compiled packages available.</p>
|
||||
</section>
|
||||
<section id="try-using-a-reticulum-based-program">
|
||||
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to try using a program built with Reticulum, a few different
|
||||
@@ -253,6 +273,13 @@ links, such as local WiFi, wired Ethernet, the Internet, or any combination.</p>
|
||||
transceivers or infrastructure just to try it out. Launching the programs on separate
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.</p>
|
||||
<section id="remote-shell">
|
||||
<h3>Remote Shell<a class="headerlink" href="#remote-shell" title="Permalink to this heading">#</a></h3>
|
||||
<p>The <a class="reference external" href="https://github.com/acehoss/rnsh">rnsh</a> program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how <code class="docutils literal notranslate"><span class="pre">ssh</span></code> works. The <code class="docutils literal notranslate"><span class="pre">rnsh</span></code> is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links.</p>
|
||||
</section>
|
||||
<section id="nomad-network">
|
||||
<h3>Nomad Network<a class="headerlink" href="#nomad-network" title="Permalink to this heading">#</a></h3>
|
||||
<p>The terminal-based program <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>
|
||||
@@ -361,25 +388,25 @@ easier setup, use TCP.</p>
|
||||
<h2>Connect to the Public Testnet<a class="headerlink" href="#connect-to-the-public-testnet" title="Permalink to this heading">#</a></h2>
|
||||
<p>An experimental public testnet has been made accessible over both I2P and TCP. You can join it
|
||||
by adding one of the following interfaces to your <code class="docutils literal notranslate"><span class="pre">.reticulum/config</span></code> file:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># TCP/IP interface to the Dublin hub</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Dublin</span><span class="p">]]</span>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># TCP/IP interface to the RNS Amsterdam Hub</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Amsterdam</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">dublin</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">amsterdam</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4965</span>
|
||||
|
||||
<span class="c1"># TCP/IP interface to the Frankfurt hub</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Frankfurt</span><span class="p">]]</span>
|
||||
<span class="c1"># TCP/IP interface to the BetweenTheBorders Hub (community-provided)</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">BetweenTheBorders</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">frankfurt</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">5377</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">betweentheborders</span><span class="o">.</span><span class="n">com</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
|
||||
<span class="c1"># Interface to I2P hub A</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">I2P</span> <span class="n">Hub</span> <span class="n">A</span><span class="p">]]</span>
|
||||
<span class="c1"># Interface to Testnet I2P Hub</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">I2P</span> <span class="n">Hub</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">I2PInterface</span>
|
||||
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">peers</span> <span class="o">=</span> <span class="n">uxg5kubabakh3jtnvsipingbr5574dle7bubvip7llfvwx2tgrua</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
|
||||
<span class="n">peers</span> <span class="o">=</span> <span class="n">g3br23bvx3lq5uddcsjii74xgmn6y5q325ovrkq2zw2wbzbqgbuq</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Many other Reticulum instances are connecting to this testnet, and you can also join it
|
||||
@@ -400,11 +427,11 @@ digital radio transceiver, that integrates easily with Reticulum.</p>
|
||||
<p>To build one yourself requires installing a custom firmware on a supported LoRa
|
||||
development board with an auto-install script. Please see the <a class="reference internal" href="hardware.html#hardware-main"><span class="std std-ref">Communications Hardware</span></a>
|
||||
chapter for a guide. If you prefer purchasing a ready-made unit, you can refer to the
|
||||
<a class="reference internal" href="hardware.html#rnode-suppliers"><span class="std std-ref">list of suppliers</span></a>. For more information on RNode, you can also
|
||||
<span class="xref std std-ref">list of suppliers</span>. For more information on RNode, you can also
|
||||
refer to these additional external resources:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://unsigned.io/how-to-make-your-own-rnodes/">How To Make Your Own RNodes</a></p></li>
|
||||
<li><p><a class="reference external" href="https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/">Installing RNode Firmware on Compatible LoRa Devices</a></p></li>
|
||||
<li><p><a class="reference external" href="https://unsigned.io/installing-rnode-firmware-on-supported-devices/">Installing RNode Firmware on Compatible LoRa Devices</a></p></li>
|
||||
<li><p><a class="reference external" href="https://unsigned.io/private-messaging-over-lora/">Private, Secure and Uncensorable Messaging Over a LoRa Mesh</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/markqvist/RNode_Firmware/">RNode Firmware</a></p></li>
|
||||
</ul>
|
||||
@@ -417,14 +444,14 @@ and propose adding an interface for the hardware.</p>
|
||||
<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
|
||||
started is to install the latest release of Reticulum via pip:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
<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>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">pip3</span> <span class="n">install</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">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>
|
||||
@@ -435,7 +462,7 @@ likely be to look at some <a class="reference internal" href="examples.html#exam
|
||||
utilities, you’ll want to get the latest source from GitHub. In that case,
|
||||
don’t use pip, but try this recipe:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
<span class="n">pip</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span>
|
||||
|
||||
<span class="c1"># Clone repository</span>
|
||||
<span class="n">git</span> <span class="n">clone</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">markqvist</span><span class="o">/</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">git</span>
|
||||
@@ -445,25 +472,25 @@ don’t use pip, but try this recipe:</p>
|
||||
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">../</span><span class="n">RNS</span> <span class="o">./</span><span class="n">Examples</span><span class="o">/</span>
|
||||
|
||||
<span class="c1"># Run an example</span>
|
||||
<span class="n">python3</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">s</span>
|
||||
<span class="n">python</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">s</span>
|
||||
|
||||
<span class="c1"># Unless you've manually created a config file, Reticulum will do so now,</span>
|
||||
<span class="c1"># and immediately exit. Make any necessary changes to the file:</span>
|
||||
<span class="n">nano</span> <span class="o">~/.</span><span class="n">reticulum</span><span class="o">/</span><span class="n">config</span>
|
||||
|
||||
<span class="c1"># ... and launch the example again.</span>
|
||||
<span class="n">python3</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">s</span>
|
||||
<span class="n">python</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">s</span>
|
||||
|
||||
<span class="c1"># You can now repeat the process on another computer,</span>
|
||||
<span class="c1"># and run the same example with -h to get command line options.</span>
|
||||
<span class="n">python3</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">h</span>
|
||||
<span class="n">python</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">h</span>
|
||||
|
||||
<span class="c1"># Run the example in client mode to "ping" the server.</span>
|
||||
<span class="c1"># Replace the hash below with the actual destination hash of your server.</span>
|
||||
<span class="n">python3</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="mi">174</span><span class="n">a64852a75682259ad8b921b8bf416</span>
|
||||
<span class="n">python</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Echo</span><span class="o">.</span><span class="n">py</span> <span class="mi">174</span><span class="n">a64852a75682259ad8b921b8bf416</span>
|
||||
|
||||
<span class="c1"># Have a look at another example</span>
|
||||
<span class="n">python3</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Filetransfer</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">h</span>
|
||||
<span class="n">python</span> <span class="n">Examples</span><span class="o">/</span><span class="n">Filetransfer</span><span class="o">.</span><span class="n">py</span> <span class="o">-</span><span class="n">h</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>When you have experimented with the basic examples, it’s time to go read the
|
||||
@@ -472,28 +499,12 @@ your first pull request, it is probably a good idea to introduce yourself on
|
||||
the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">disucssion forum on GitHub</a>,
|
||||
or ask one of the developers or maintainers for a good place to start.</p>
|
||||
</section>
|
||||
<section id="reticulum-on-arm64">
|
||||
<h2>Reticulum on ARM64<a class="headerlink" href="#reticulum-on-arm64" title="Permalink to this heading">#</a></h2>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="reticulum-on-raspberry-pi">
|
||||
<h2>Reticulum on Raspberry Pi<a class="headerlink" href="#reticulum-on-raspberry-pi" title="Permalink to this heading">#</a></h2>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
</section>
|
||||
<section id="reticulum-on-android">
|
||||
<h2>Reticulum on Android<a class="headerlink" href="#reticulum-on-android" title="Permalink to this heading">#</a></h2>
|
||||
<section id="platform-specific-install-notes">
|
||||
<h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
|
||||
<p>Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.</p>
|
||||
<section id="android">
|
||||
<h3>Android<a class="headerlink" href="#android" title="Permalink to this heading">#</a></h3>
|
||||
<p>Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>.</p>
|
||||
<p>For more control and features, you can use Reticulum and related programs via
|
||||
@@ -551,12 +562,92 @@ locally on your device using the following command:</p>
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the <a class="reference external" href="https://github.com/markqvist/sideband">Sideband source code</a> as an example and startig point.</p>
|
||||
</section>
|
||||
<section id="arm64">
|
||||
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="raspberry-pi">
|
||||
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.</p>
|
||||
</section>
|
||||
<section id="debian-bookworm">
|
||||
<h3>Debian Bookworm<a class="headerlink" href="#debian-bookworm" title="Permalink to this heading">#</a></h3>
|
||||
<p>On versions of Debian released after April 2023, it is no longer possible by default
|
||||
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but will not work
|
||||
for including and using Reticulum in your own scripts and programs.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
|
||||
|
||||
<span class="c1"># Make installed programs available on the command line</span>
|
||||
<span class="n">pipx</span> <span class="n">ensurepath</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Alternatively, you can restore normal behaviour to <code class="docutils literal notranslate"><span class="pre">pip</span></code> by creating or editing
|
||||
the configuration file located at <code class="docutils literal notranslate"><span class="pre">~/.config/pip/pip.conf</span></code>, and adding the
|
||||
following section:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[global]
|
||||
break-system-packages = true
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>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 <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>
|
||||
</section>
|
||||
<section id="ubuntu-lunar">
|
||||
<h3>Ubuntu Lunar<a class="headerlink" href="#ubuntu-lunar" title="Permalink to this heading">#</a></h3>
|
||||
<p>On versions of Ubuntu released after April 2023, it is no longer possible by default
|
||||
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but will not work
|
||||
for including and using Reticulum in your own scripts and programs.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
|
||||
|
||||
<span class="c1"># Make installed programs available on the command line</span>
|
||||
<span class="n">pipx</span> <span class="n">ensurepath</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Alternatively, you can restore normal behaviour to <code class="docutils literal notranslate"><span class="pre">pip</span></code> by creating or editing
|
||||
the configuration file located at <code class="docutils literal notranslate"><span class="pre">~/.config/pip/pip.conf</span></code>, and adding the
|
||||
following section:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[global]
|
||||
break-system-packages = true
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>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 <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>
|
||||
</section>
|
||||
</section>
|
||||
<section id="pure-python-reticulum">
|
||||
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this heading">#</a></h2>
|
||||
<p>In some rare cases, and on more obscure system types, it is not possible to
|
||||
install one or more dependencies</p>
|
||||
<p>On more unusual systems, and in some rare cases, it might not be possible to
|
||||
install or even compile one or more of the above modules. In such situations,
|
||||
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>
|
||||
with the <code class="docutils literal notranslate"><span class="pre">--no-dependencies</span></code> command-line option. The <code class="docutils literal notranslate"><span class="pre">rnspure</span></code>
|
||||
package requires no external dependencies for installation. Please note that the
|
||||
@@ -604,7 +695,7 @@ section of this manual.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -633,7 +724,9 @@ section of this manual.</p>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
|
||||
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li><a class="reference internal" href="#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
|
||||
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
|
||||
<li><a class="reference internal" href="#sideband">Sideband</a></li>
|
||||
</ul>
|
||||
@@ -645,9 +738,14 @@ section of this manual.</p>
|
||||
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-raspberry-pi">Reticulum on Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-android">Reticulum on Android</a></li>
|
||||
<li><a class="reference internal" href="#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li><a class="reference internal" href="#android">Android</a></li>
|
||||
<li><a class="reference internal" href="#arm64">ARM64</a></li>
|
||||
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li><a class="reference internal" href="#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -660,14 +758,11 @@ section of this manual.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+59
-42
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Supported Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<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.2.2, furo 2022.09.29"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -240,10 +240,17 @@ starting from scratch.</p>
|
||||
<p>This chapter will outline a few different sensible starting paths to get
|
||||
real-world functional wireless communications up and running with minimal cost
|
||||
and effort. Two fundamental devices categories will be covered, <em>RNodes</em> and
|
||||
<em>WiFi-based radios</em>.</p>
|
||||
<p>While there are many other device categories that are useful in building Reticulum
|
||||
networks, knowing how to employ just these two will make it possible to build
|
||||
a wide range of useful networks with little effort.</p>
|
||||
<em>WiFi-based radios</em>. Additionally, other common options will be briefly described.</p>
|
||||
<p>Knowing how to employ just a few different types of hardware will make it possible
|
||||
to build a wide range of useful networks with little effort.</p>
|
||||
<section id="combining-hardware-types">
|
||||
<h2>Combining Hardware Types<a class="headerlink" href="#combining-hardware-types" title="Permalink to this heading">#</a></h2>
|
||||
<p>It is useful to combine different link and hardware types when designing and
|
||||
building a network. One useful design pattern is to employ high-capacity point-to-point
|
||||
links based on WiFi or millimeter-wave radios (with high-gain directional antennas)
|
||||
for the network backbone, and using LoRa-based RNodes for covering large areas with
|
||||
connectivity for client devices.</p>
|
||||
</section>
|
||||
<section id="rnode">
|
||||
<span id="rnode-main"></span><h2>RNode<a class="headerlink" href="#rnode" title="Permalink to this heading">#</a></h2>
|
||||
<p>Reliable and general-purpose long-range digital radio transceiver systems are
|
||||
@@ -373,11 +380,6 @@ such as serial port and on-air parameters. For v2.x firmwares, you just need to
|
||||
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
|
||||
RNode, using the parameters stored in the RNode itself.</p>
|
||||
</section>
|
||||
<section id="suppliers">
|
||||
<span id="rnode-suppliers"></span><h3>Suppliers<a class="headerlink" href="#suppliers" title="Permalink to this heading">#</a></h3>
|
||||
<p>Get in touch if you want to have your RNode supplier listed here, or if you want help to
|
||||
get started with producing RNodes.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="wifi-based-hardware">
|
||||
<h2>WiFi-based Hardware<a class="headerlink" href="#wifi-based-hardware" title="Permalink to this heading">#</a></h2>
|
||||
@@ -403,13 +405,29 @@ that is relatively cheap while providing long range and high capacity for Reticu
|
||||
networks. As in all other cases, it is also possible for Reticulum to co-exist with IP
|
||||
networks running concurrently on such devices.</p>
|
||||
</section>
|
||||
<section id="combining-hardware-types">
|
||||
<h2>Combining Hardware Types<a class="headerlink" href="#combining-hardware-types" title="Permalink to this heading">#</a></h2>
|
||||
<p>It is useful to combine different link and hardware types when designing and
|
||||
building a network. One useful design pattern is to employ high-capacity point-to-point
|
||||
links based on WiFi or millimeter-wave radios (with high-gain directional antennas)
|
||||
for the network backbone, and using LoRa-based RNodes for covering large areas with
|
||||
connectivity for client devices.</p>
|
||||
<section id="ethernet-based-hardware">
|
||||
<h2>Ethernet-based Hardware<a class="headerlink" href="#ethernet-based-hardware" title="Permalink to this heading">#</a></h2>
|
||||
<p>Reticulum can run over any kind of hardware that can provide a switched Ethernet-based
|
||||
medium. This means that anything from a plain Ethernet switch, to fiber-optic systems,
|
||||
to data radios with Ethernet interfaces can be used by Reticulum.</p>
|
||||
<p>The Ethernet medium does not need to have any IP infrastructure such as DHCP servers
|
||||
or routing set up, but in case such infrastructure does exist, Reticulum will simply
|
||||
co-exist with.</p>
|
||||
<p>To use Reticulum over Ethernet-based mediums, it is generally enough to use the included
|
||||
<a class="reference internal" href="interfaces.html#interfaces-auto"><span class="std std-ref">AutoInterface</span></a>. This interface also works over any kind of
|
||||
virtual networking adapter, such as <code class="docutils literal notranslate"><span class="pre">tun</span></code> and <code class="docutils literal notranslate"><span class="pre">tap</span></code> devices in Linux.</p>
|
||||
</section>
|
||||
<section id="serial-lines-devices">
|
||||
<h2>Serial Lines & Devices<a class="headerlink" href="#serial-lines-devices" title="Permalink to this heading">#</a></h2>
|
||||
<p>Using Reticulum over any kind of raw serial line is also possible with the
|
||||
<a class="reference internal" href="interfaces.html#interfaces-serial"><span class="std std-ref">SerialInterface</span></a>. This interface type is also useful for
|
||||
using Reticulum over communications hardware that provides a serial port interface.</p>
|
||||
</section>
|
||||
<section id="packet-radio-modems">
|
||||
<h2>Packet Radio Modems<a class="headerlink" href="#packet-radio-modems" title="Permalink to this heading">#</a></h2>
|
||||
<p>Any packet radio modem that provides a standard KISS interface over USB, serial or TCP
|
||||
can be used with Reticulum. This includes virtual software modems such as
|
||||
<a class="reference external" href="https://github.com/xssfox/freedv-tnc">FreeDV TNC</a> and <a class="reference external" href="https://github.com/wb2osz/direwolf">Dire Wolf</a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -423,7 +441,7 @@ connectivity for client devices.</p>
|
||||
<div class="context">
|
||||
<span>Next</span>
|
||||
</div>
|
||||
<div class="title">Supported Interfaces</div>
|
||||
<div class="title">Configuring Interfaces</div>
|
||||
</div>
|
||||
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
|
||||
</a>
|
||||
@@ -442,7 +460,7 @@ connectivity for client devices.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -470,6 +488,7 @@ connectivity for client devices.</p>
|
||||
<div class="toc-tree">
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Communications Hardware</a><ul>
|
||||
<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>
|
||||
@@ -483,11 +502,12 @@ connectivity for client devices.</p>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#installation">Installation</a></li>
|
||||
<li><a class="reference internal" href="#usage-with-reticulum">Usage with Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#suppliers">Suppliers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#wifi-based-hardware">WiFi-based Hardware</a></li>
|
||||
<li><a class="reference internal" href="#combining-hardware-types">Combining Hardware Types</a></li>
|
||||
<li><a class="reference internal" href="#ethernet-based-hardware">Ethernet-based Hardware</a></li>
|
||||
<li><a class="reference internal" href="#serial-lines-devices">Serial Lines & Devices</a></li>
|
||||
<li><a class="reference internal" href="#packet-radio-modems">Packet Radio Modems</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -499,14 +519,11 @@ connectivity for client devices.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+44
-28
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="#">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="#">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -226,6 +226,7 @@
|
||||
<p>This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.</p>
|
||||
<p>This manual is also available in <a class="reference external" href="https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf">PDF</a> and <a class="reference external" href="https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub">EPUB</a> formats.</p>
|
||||
<section id="table-of-contents">
|
||||
<h2>Table Of Contents<a class="headerlink" href="#table-of-contents" title="Permalink to this heading">#</a></h2>
|
||||
</section>
|
||||
@@ -241,7 +242,9 @@ to participate in the development of Reticulum itself.</p>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#sideband">Sideband</a></li>
|
||||
</ul>
|
||||
@@ -253,9 +256,14 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-raspberry-pi">Reticulum on Raspberry Pi</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-android">Reticulum on Android</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#android">Android</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#arm64">ARM64</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -264,6 +272,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="using.html#included-utility-programs">Included Utility Programs</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnsd-utility">The rnsd Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnstatus-utility">The rnstatus Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnid-utility">The rnid Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnpath-utility">The rnpath Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnprobe-utility">The rnprobe Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rncp-utility">The rncp Utility</a></li>
|
||||
@@ -307,19 +316,21 @@ to participate in the development of Reticulum itself.</p>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a><ul>
|
||||
<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#installation">Installation</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#usage-with-reticulum">Usage with Reticulum</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#suppliers">Suppliers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="hardware.html#wifi-based-hardware">WiFi-based Hardware</a></li>
|
||||
<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#ethernet-based-hardware">Ethernet-based Hardware</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="hardware.html#serial-lines-devices">Serial Lines & Devices</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="hardware.html#packet-radio-modems">Packet Radio Modems</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a><ul>
|
||||
<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#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>
|
||||
@@ -333,6 +344,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#common-interface-options">Common Interface Options</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#interface-modes">Interface Modes</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#announce-rate-control">Announce Rate Control</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#new-destination-rate-limiting">New Destination Rate Limiting</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
|
||||
@@ -353,6 +365,8 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#link">Link</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#example-identify">Identification</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#requests-responses">Requests & Responses</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#channel">Channel</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#buffer">Buffer</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -375,6 +389,11 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Link"><code class="docutils literal notranslate"><span class="pre">Link</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RequestReceipt"><code class="docutils literal notranslate"><span class="pre">RequestReceipt</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Resource"><code class="docutils literal notranslate"><span class="pre">Resource</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Channel.Channel"><code class="docutils literal notranslate"><span class="pre">Channel</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Buffer"><code class="docutils literal notranslate"><span class="pre">Buffer</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RawChannelReader"><code class="docutils literal notranslate"><span class="pre">RawChannelReader</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RawChannelWriter"><code class="docutils literal notranslate"><span class="pre">RawChannelWriter</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Transport"><code class="docutils literal notranslate"><span class="pre">Transport</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -408,7 +427,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -449,14 +468,11 @@ to participate in the development of Reticulum itself.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+123
-26
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -221,8 +221,8 @@
|
||||
</label>
|
||||
</div>
|
||||
<article role="main">
|
||||
<section id="supported-interfaces">
|
||||
<span id="interfaces-main"></span><h1>Supported Interfaces<a class="headerlink" href="#supported-interfaces" title="Permalink to this heading">#</a></h1>
|
||||
<section id="configuring-interfaces">
|
||||
<span id="interfaces-main"></span><h1>Configuring Interfaces<a class="headerlink" href="#configuring-interfaces" title="Permalink to this heading">#</a></h1>
|
||||
<p>Reticulum supports using many kinds of devices as networking interfaces, and
|
||||
allows you to mix and match them in any way you choose. The number of distinct
|
||||
network topologies you can create with Reticulum is more or less endless, but
|
||||
@@ -527,6 +527,7 @@ can be used, and offers full control over LoRa parameters.</p>
|
||||
<span class="c1"># out identification on the channel with</span>
|
||||
<span class="c1"># a set interval by configuring the</span>
|
||||
<span class="c1"># following two parameters.</span>
|
||||
|
||||
<span class="c1"># id_callsign = MYCALL-0</span>
|
||||
<span class="c1"># id_interval = 600</span>
|
||||
|
||||
@@ -534,7 +535,20 @@ can be used, and offers full control over LoRa parameters.</p>
|
||||
<span class="c1"># with low amounts of RAM, using packet</span>
|
||||
<span class="c1"># flow control can be useful. By default</span>
|
||||
<span class="c1"># it is disabled.</span>
|
||||
<span class="n">flow_control</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="c1"># flow_control = False</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 = 1.5</span>
|
||||
<span class="c1"># airtime_limit_short = 33</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -923,6 +937,91 @@ conserve bandwidth, while very fast networks can support applications that
|
||||
need very frequent announces. Reticulum implements these mechanisms to ensure
|
||||
that a large span of network types can seamlessly <em>co-exist</em> and interconnect.</p>
|
||||
</section>
|
||||
<section id="new-destination-rate-limiting">
|
||||
<span id="interfaces-ingress-control"></span><h2>New Destination Rate Limiting<a class="headerlink" href="#new-destination-rate-limiting" title="Permalink to this heading">#</a></h2>
|
||||
<p>On public interfaces, where anyone may connect and announce new destinations,
|
||||
it can be useful to control the rate at which announces for <em>new</em> destinations are
|
||||
processed.</p>
|
||||
<p>If a large influx of announces for newly created or previously unknown destinations
|
||||
occur within a short amount of time, Reticulum will place these announces on hold,
|
||||
so that announce traffic for known and previously established destinations can
|
||||
continue to be processed without interruptions.</p>
|
||||
<p>After the burst subsides, and an additional waiting period has passed, the held
|
||||
announces will be released at a slow rate, until the hold queue is cleared. This
|
||||
also means, that should a node decide to connect to a public interface, announce
|
||||
a large amount of bogus destinations, and then disconnect, these destination will
|
||||
never make it into path tables and waste network bandwidth on retransmitted
|
||||
announces.</p>
|
||||
<p><strong>It’s important to note</strong> that the ingress control works at the level of <em>individual
|
||||
sub-interfaces</em>. As an example, this means that one client on a <a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
<a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>. All other clients on the same interface will still have new announces
|
||||
processed without interruption.</p>
|
||||
<p>By default, Reticulum will handle this automatically, and ingress announce
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.</p>
|
||||
<blockquote>
|
||||
<div><ul>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ingress_control</span></code> option tells Reticulum whether or not
|
||||
to enable announce ingress control on the interface. Defaults to
|
||||
<code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_new_time</span></code> option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to <code class="docutils literal notranslate"><span class="pre">2*60*60</span></code> seconds. This
|
||||
option is useful on publicly accessible interfaces that spawn new
|
||||
sub-interfaces when a new client connects.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_burst_freq_new</span></code> option sets the maximum announce ingress
|
||||
frequency for newly spawned interfaces. Defaults to <code class="docutils literal notranslate"><span class="pre">3.5</span></code>
|
||||
announces per second.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_burst_freq</span></code> option sets the maximum announce ingress
|
||||
frequency for other interfaces. Defaults to <code class="docutils literal notranslate"><span class="pre">12</span></code> announces
|
||||
per second.</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<div><p><em>If an interface exceeds its burst frequency, incoming announces
|
||||
for unknown destinations will be temporarily held in a queue, and
|
||||
not processed until later.</em></p>
|
||||
</div></blockquote>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_max_held_announces</span></code> option sets the maximum amount of
|
||||
unique announces that will be held in the queue. Any additional
|
||||
unique announces will be dropped. Defaults to <code class="docutils literal notranslate"><span class="pre">256</span></code> announces.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_burst_hold</span></code> option sets how much time (in seconds) must
|
||||
pass after the burst frequency drops below its threshold, for the
|
||||
announce burst to be considered cleared. Defaults to <code class="docutils literal notranslate"><span class="pre">60</span></code>
|
||||
seconds.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_burst_penalty</span></code> option sets how much time (in seconds) must
|
||||
pass after the burst is considered cleared, before held announces can
|
||||
start being released from the queue. Defaults to <code class="docutils literal notranslate"><span class="pre">5*60</span></code>
|
||||
seconds.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_held_release_interval</span></code> option sets how much time (in seconds)
|
||||
must pass between releasing each held announce from the queue. Defaults
|
||||
to <code class="docutils literal notranslate"><span class="pre">30</span></code> seconds.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
@@ -954,7 +1053,7 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -981,7 +1080,7 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
|
||||
<div class="toc-tree-container">
|
||||
<div class="toc-tree">
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Supported Interfaces</a><ul>
|
||||
<li><a class="reference internal" href="#">Configuring Interfaces</a><ul>
|
||||
<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>
|
||||
@@ -995,6 +1094,7 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
|
||||
<li><a class="reference internal" href="#common-interface-options">Common Interface Options</a></li>
|
||||
<li><a class="reference internal" href="#interface-modes">Interface Modes</a></li>
|
||||
<li><a class="reference internal" href="#announce-rate-control">Announce Rate Control</a></li>
|
||||
<li><a class="reference internal" href="#new-destination-rate-limiting">New Destination Rate Limiting</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1006,14 +1106,11 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+21
-24
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Supported Interfaces" href="interfaces.html" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<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.2.2, furo 2022.09.29"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -413,7 +413,7 @@ connected outliers are now an integral part of the network.</p>
|
||||
<span>Previous</span>
|
||||
</div>
|
||||
|
||||
<div class="title">Supported Interfaces</div>
|
||||
<div class="title">Configuring Interfaces</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
@@ -421,7 +421,7 @@ connected outliers are now an integral part of the network.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -467,14 +467,11 @@ connected outliers are now an integral part of the network.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
+571
-140
File diff suppressed because it is too large
Load Diff
+16
-19
@@ -4,11 +4,11 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.4.8 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.6.8 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -158,16 +158,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 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">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -178,7 +178,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -241,7 +241,7 @@
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -262,15 +262,12 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
|
||||
<script src="_static/searchtools.js"></script>
|
||||
<script src="_static/language_data.js"></script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
+19
-22
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Support Reticulum</a></li>
|
||||
@@ -288,7 +288,7 @@ report issues, suggest functionality and contribute code to Reticulum.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -330,14 +330,11 @@ report issues, suggest functionality and contribute code to Reticulum.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -286,7 +286,7 @@ it can be easily modified and replicated by anyone interested in doing so.</p>
|
||||
</li>
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Very low bandwidth requirements</strong></dt><dd><p>Reticulum should be able to function reliably over links with a transmission capacity as low
|
||||
as <em>500 bits per second</em>.</p>
|
||||
as <em>5 bits per second</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -831,7 +831,7 @@ note that Reticulum is designed to be usable on more or less any computing devic
|
||||
or less any medium that allows you to send and receive data, which satisfies some very low
|
||||
minimum requirements.</p>
|
||||
<p>The communication channel must support at least half-duplex operation, and provide an average
|
||||
throughput of around 500 bits per second, and supports a physical layer MTU of 500 bytes. The
|
||||
throughput of 5 bits per second or greater, and supports a physical layer MTU of 500 bytes. The
|
||||
Reticulum stack should be able to run on more or less any hardware that can provide a Python 3.x
|
||||
runtime environment.</p>
|
||||
<p>That being said, this reference setup has been outlined to provide a common platform for anyone
|
||||
@@ -1061,7 +1061,7 @@ cryptographic primitives, with widely available implementations that can be used
|
||||
both on general-purpose CPUs and on microcontrollers. The necessary primitives are:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Ed25519 for signatures</p></li>
|
||||
<li><p>X22519 for ECDH key exchanges</p></li>
|
||||
<li><p>X25519 for ECDH key exchanges</p></li>
|
||||
<li><p>HKDF for key derivation</p></li>
|
||||
<li><p>Fernet for encrypted tokens</p>
|
||||
<ul>
|
||||
@@ -1126,7 +1126,7 @@ those risks are acceptable to you.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -1196,14 +1196,11 @@ those risks are acceptable to you.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
+332
-90
@@ -2,16 +2,16 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.4.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.6.8 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.8 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.8 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -161,16 +161,16 @@
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.8 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.8 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
@@ -181,7 +181,7 @@
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
@@ -307,6 +307,17 @@ configuration file is created. The default configuration looks like this:</p>
|
||||
<span class="n">instance_control_port</span> <span class="o">=</span> <span class="mi">37429</span>
|
||||
|
||||
|
||||
<span class="c1"># On systems where running instances may not have access</span>
|
||||
<span class="c1"># to the same shared Reticulum configuration directory,</span>
|
||||
<span class="c1"># it is still possible to allow full interactivity for</span>
|
||||
<span class="c1"># running instances, by manually specifying a shared RPC</span>
|
||||
<span class="c1"># key. In almost all cases, this option is not needed, but</span>
|
||||
<span class="c1"># it can be useful on operating systems such as Android.</span>
|
||||
<span class="c1"># The key must be specified as bytes in hexadecimal.</span>
|
||||
|
||||
<span class="c1"># rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790</span>
|
||||
|
||||
|
||||
<span class="c1"># You can configure Reticulum to panic and forcibly close</span>
|
||||
<span class="c1"># if an unrecoverable interface error occurs, such as the</span>
|
||||
<span class="c1"># hardware device for an interface disappearing. This is</span>
|
||||
@@ -316,6 +327,17 @@ configuration file is created. The default configuration looks like this:</p>
|
||||
<span class="n">panic_on_interface_error</span> <span class="o">=</span> <span class="n">No</span>
|
||||
|
||||
|
||||
<span class="c1"># When Transport is enabled, it is possible to allow the</span>
|
||||
<span class="c1"># Transport Instance to respond to probe requests from</span>
|
||||
<span class="c1"># the rnprobe utility. This can be a useful tool to test</span>
|
||||
<span class="c1"># connectivity. When this option is enabled, the probe</span>
|
||||
<span class="c1"># destination will be generated from the Identity of the</span>
|
||||
<span class="c1"># Transport Instance, and printed to the log at startup.</span>
|
||||
<span class="c1"># Optional, and disabled by default.</span>
|
||||
|
||||
<span class="n">respond_to_probes</span> <span class="o">=</span> <span class="n">No</span>
|
||||
|
||||
|
||||
<span class="p">[</span><span class="n">logging</span><span class="p">]</span>
|
||||
<span class="c1"># Valid log levels are 0 through 7:</span>
|
||||
<span class="c1"># 0: Log only critical information</span>
|
||||
@@ -354,10 +376,15 @@ configuration file is created. The default configuration looks like this:</p>
|
||||
<p>If Reticulum infrastructure already exists locally, you probably don’t need to
|
||||
change anything, and you may already be connected to a wider network. If not,
|
||||
you will probably need to add relevant <em>interfaces</em> to the configuration, in
|
||||
order to communicate with other systems. It is a good idea to read the comments
|
||||
and explanations in the above default config. It will teach you the basic
|
||||
concepts you need to understand to configure your network. Once you have done that,
|
||||
take a look at the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Interfaces</span></a> chapter of this manual.</p>
|
||||
order to communicate with other systems.</p>
|
||||
<p>You can generate a much more verbose configuration example by running the command:</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">rnsd</span> <span class="pre">--exampleconfig</span></code></p>
|
||||
<p>The output includes examples for most interface types supported
|
||||
by Reticulum, along with additional options and configuration parameters.</p>
|
||||
<p>It is a good idea to read the comments and explanations in the above default config.
|
||||
It will teach you the basic concepts you need to understand to configure your network.
|
||||
Once you have done that, take a look at the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Interfaces</span></a> chapter
|
||||
of this manual.</p>
|
||||
</section>
|
||||
<section id="included-utility-programs">
|
||||
<h2>Included Utility Programs<a class="headerlink" href="#included-utility-programs" title="Permalink to this heading">#</a></h2>
|
||||
@@ -374,24 +401,36 @@ other programs, applications and services can utilise.</p>
|
||||
When <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> is running, it will keep all configured interfaces open, handle transport if
|
||||
it is enabled, and allow any other programs to immediately utilise the
|
||||
Reticulum network it is configured for.</p>
|
||||
<p>You can even run multiple instances of rnsd with different configurations on
|
||||
<p>You can even run multiple instances of <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> with different configurations on
|
||||
the same system.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Install Reticulum
|
||||
pip3 install rns
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Run <code class="docutils literal notranslate"><span class="pre">rnsd</span></code>:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnsd
|
||||
|
||||
# Run rnsd
|
||||
rnsd
|
||||
[2023-08-18 17:59:56] [Notice] Started rnsd version 0.5.8
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
<p>Run <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> in service mode, ensuring all logging output is sent directly to file:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnsd -s
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Generate a verbose and detailed configuration example, with explanations of all the
|
||||
various configuration options, and interface configuration examples:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnsd --exampleconfig
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnsd.py [-h] [--config CONFIG] [-v] [-q] [-s] [--exampleconfig] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
-s, --service rnsd is running as a service and should log to file
|
||||
--exampleconfig print verbose configuration example to stdout and exit
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -401,10 +440,10 @@ optional arguments:
|
||||
<h3>The rnstatus Utility<a class="headerlink" href="#the-rnstatus-utility" title="Permalink to this heading">#</a></h3>
|
||||
<p>Using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the <code class="docutils literal notranslate"><span class="pre">ifconfig</span></code> program.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnstatus
|
||||
rnstatus
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Run <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code>:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnstatus
|
||||
|
||||
# Example output
|
||||
Shared Instance[37428]
|
||||
Status : Up
|
||||
Serving : 1 program
|
||||
@@ -420,7 +459,7 @@ AutoInterface[Local]
|
||||
Traffic : 63.23 KB↑
|
||||
80.17 KB↓
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
|
||||
Status : Up
|
||||
Mode : Full
|
||||
Rate : 10.00 Mbps
|
||||
@@ -438,38 +477,147 @@ RNodeInterface[RNode UHF]
|
||||
Reticulum Transport Instance <5245a8efe1788c6a1cd36144a270e13b> running
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-v]
|
||||
<p>Filter output to only show some interfaces:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnstatus rnode
|
||||
|
||||
RNodeInterface[RNode UHF]
|
||||
Status : Up
|
||||
Mode : Access Point
|
||||
Rate : 1.30 kbps
|
||||
Access : 64-bit IFAC by <…e702c42ba8>
|
||||
Traffic : 8.49 KB↑
|
||||
9.23 KB↓
|
||||
|
||||
Reticulum Transport Instance <5245a8efe1788c6a1cd36144a270e13b> running
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
|
||||
[-r] [-j] [-v] [filter]
|
||||
|
||||
Reticulum Network Stack Status
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
positional arguments:
|
||||
filter only display interfaces with names including filter
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
|
||||
-r, --reverse reverse sorting
|
||||
-j, --json output in JSON format
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="the-rnid-utility">
|
||||
<h3>The rnid Utility<a class="headerlink" href="#the-rnid-utility" title="Permalink to this heading">#</a></h3>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">rnid</span></code> utility, you can generate, manage and view Reticulum Identities.
|
||||
The program can also calculate Destination hashes, and perform encryption and
|
||||
decryption of files.</p>
|
||||
<p>Using <code class="docutils literal notranslate"><span class="pre">rnid</span></code>, it is possible to asymmetrically encrypt files and information for
|
||||
any Reticulum destination hash, and also to create and verify cryptographic signatures.</p>
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Generate a new Identity:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnid -g ./new_identity
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Display Identity key information:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnid -i ./new_identity -p
|
||||
|
||||
Loaded Identity <984b74a3f768bef236af4371e6f248cd> from new_id
|
||||
Public Key : 0f4259fef4521ab75a3409e353fe9073eb10783b4912a6a9937c57bf44a62c1e
|
||||
Private Key : Hidden
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Encrypt a file for an LXMF user:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnid -i 8dd57a738226809646089335a6b03695 -e my_file.txt
|
||||
|
||||
Recalled Identity <bc7291552be7a58f361522990465165c> for destination <8dd57a738226809646089335a6b03695>
|
||||
Encrypting my_file.txt
|
||||
File my_file.txt encrypted for <bc7291552be7a58f361522990465165c> to my_file.txt.rfe
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If the Identity for the destination is not already known, you can fetch it from the network by using the <code class="docutils literal notranslate"><span class="pre">-R</span></code> command-line option:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnid -R -i 30602def3b3506a28ed33db6f60cc6c9 -e my_file.txt
|
||||
|
||||
Requesting unknown Identity for <30602def3b3506a28ed33db6f60cc6c9>...
|
||||
Received Identity <2b489d06eaf7c543808c76a5332a447d> for destination <30602def3b3506a28ed33db6f60cc6c9> from the network
|
||||
Encrypting my_file.txt
|
||||
File my_file.txt encrypted for <2b489d06eaf7c543808c76a5332a447d> to my_file.txt.rfe
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Decrypt a file using the Reticulum Identity it was encrypted for:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnid -i ./my_identity -d my_file.txt.rfe
|
||||
|
||||
Loaded Identity <2225fdeecaf6e2db4556c3c2d7637294> from ./my_identity
|
||||
Decrypting ./my_file.txt.rfe...
|
||||
File ./my_file.txt.rfe decrypted with <2225fdeecaf6e2db4556c3c2d7637294> to ./my_file.txt
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnid.py [-h] [--config path] [-i identity] [-g path] [-v] [-q] [-a aspects]
|
||||
[-H aspects] [-e path] [-d path] [-s path] [-V path] [-r path] [-w path]
|
||||
[-f] [-R] [-t seconds] [-p] [-P] [--version]
|
||||
|
||||
Reticulum Identity & Encryption Utility
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config path path to alternative Reticulum config directory
|
||||
-i identity, --identity identity
|
||||
hexadecimal Reticulum Destination hash or path to Identity file
|
||||
-g path, --generate path
|
||||
generate a new Identity
|
||||
-v, --verbose increase verbosity
|
||||
-q, --quiet decrease verbosity
|
||||
-a aspects, --announce aspects
|
||||
announce a destination based on this Identity
|
||||
-H aspects, --hash aspects
|
||||
show destination hashes for other aspects for this Identity
|
||||
-e path, --encrypt path
|
||||
encrypt file
|
||||
-d path, --decrypt path
|
||||
decrypt file
|
||||
-s path, --sign path sign file
|
||||
-V path, --validate path
|
||||
validate signature
|
||||
-r path, --read path input file path
|
||||
-w path, --write path
|
||||
output file path
|
||||
-f, --force write output even if it overwrites existing files
|
||||
-R, --request request unknown Identities from the network
|
||||
-t seconds identity request timeout before giving up
|
||||
-p, --print-identity print identity info and exit
|
||||
-P, --print-private allow displaying private keys
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="the-rnpath-utility">
|
||||
<h3>The rnpath Utility<a class="headerlink" href="#the-rnpath-utility" title="Permalink to this heading">#</a></h3>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">rnpath</span></code> utility, you can look up and view paths for
|
||||
destinations on the Reticulum network.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnpath
|
||||
rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Resolve path to a destination:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
|
||||
# Example output
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
|
||||
[-x] [-w seconds] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
positional arguments:
|
||||
destination hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
@@ -477,6 +625,7 @@ optional arguments:
|
||||
-r, --rates show announce rate info
|
||||
-d, --drop remove the path to a destination
|
||||
-D, --drop-announces drop all queued announces
|
||||
-x, --drop-via drop all paths via specified transport instance
|
||||
-w seconds timeout before giving up
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
@@ -487,17 +636,41 @@ optional arguments:
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnprobe</span></code> utility lets you probe a destination for connectivity, similar
|
||||
to the <code class="docutils literal notranslate"><span class="pre">ping</span></code> program. Please note that probes will only be answered if the
|
||||
specified destination is configured to send proofs for received packets. Many
|
||||
destinations will not have this option enabled, and will not be probable.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnprobe
|
||||
rnprobe example_utilities.echo.request 2d03725b327348980d570f739a3a5708
|
||||
destinations will not have this option enabled, so most destinations will not
|
||||
be probable.</p>
|
||||
<p>You can enable a probe-reply destination on Reticulum Transport Instances by
|
||||
setting the <code class="docutils literal notranslate"><span class="pre">respond_to_probes</span></code> configuration directive. Reticulum will then
|
||||
print the probe destination to the log on Transport Instance startup.</p>
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Probe a destination:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnprobe rnstransport.probe 2d03725b327348980d570f739a3a5708
|
||||
|
||||
# Example output
|
||||
Sent 16 byte probe to <2d03725b327348980d570f739a3a5708>
|
||||
Valid reply received from <2d03725b327348980d570f739a3a5708>
|
||||
Round-trip time is 38.469 milliseconds over 2 hops
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
|
||||
<p>Send a larger probe:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnprobe rnstransport.probe 2d03725b327348980d570f739a3a5708 -s 256
|
||||
|
||||
Sent 16 byte probe to <2d03725b327348980d570f739a3a5708>
|
||||
Valid reply received from <2d03725b327348980d570f739a3a5708>
|
||||
Round-trip time is 38.781 milliseconds over 2 hops
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If the interface that receives the probe replies supports reporting radio
|
||||
parameters such as <strong>RSSI</strong> and <strong>SNR</strong>, the <code class="docutils literal notranslate"><span class="pre">rnprobe</span></code> utility will print
|
||||
these as part of the result as well.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnprobe rnstransport.probe e7536ee90bd4a440e130490b87a25124
|
||||
|
||||
Sent 16 byte probe to <e7536ee90bd4a440e130490b87a25124>
|
||||
Valid reply received from <e7536ee90bd4a440e130490b87a25124>
|
||||
Round-trip time is 1.809 seconds over 1 hop [RSSI -73 dBm] [SNR 12.0 dB]
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
|
||||
[full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
@@ -508,6 +681,7 @@ positional arguments:
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-s SIZE, --size SIZE size of probe packet payload in bytes
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
@@ -517,16 +691,27 @@ optional arguments:
|
||||
<h3>The rncp Utility<a class="headerlink" href="#the-rncp-utility" title="Permalink to this heading">#</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rncp</span></code> utility is a simple file transfer tool. Using it, you can transfer
|
||||
files through Reticulum.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rncp on the receiving system, specifying which identities
|
||||
# are allowed to send files
|
||||
rncp --receive -a 1726dbad538775b5bf9b0ea25a4079c8 -a c50cc4e4f7838b6c31f60ab9032cbc62
|
||||
|
||||
# From another system, copy a file to the receiving system
|
||||
rncp ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Run rncp on the receiving system, specifying which identities are allowed to send files:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rncp --listen -a 1726dbad538775b5bf9b0ea25a4079c8 -a c50cc4e4f7838b6c31f60ab9032cbc62
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can specify as many allowed senders as needed, or complete disable authentication.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
|
||||
<p>You can also specify allowed identity hashes (one per line) in the file ~/.rncp/allowed_identities
|
||||
and simply running the program in listener mode:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rncp --listen
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>From another system, copy a file to the receiving system:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rncp ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Or fetch a file from the remote system:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rncp --fetch ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
|
||||
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
|
||||
|
||||
Reticulum File Transfer Utility
|
||||
|
||||
@@ -534,19 +719,20 @@ positional arguments:
|
||||
file file to be transferred
|
||||
destination hexadecimal hash of the receiver
|
||||
|
||||
optional arguments:
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config path path to alternative Reticulum config directory
|
||||
-v, --verbose increase verbosity
|
||||
-q, --quiet decrease verbosity
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-r, --receive wait for incoming files
|
||||
-b, --no-announce don't announce at program start
|
||||
-S, --silent disable transfer progress output
|
||||
-l, --listen listen for incoming transfer requests
|
||||
-f, --fetch fetch file from remote listener instead of sending
|
||||
-b seconds announce interval, 0 to only announce at startup
|
||||
-a allowed_hash accept from this identity
|
||||
-n, --no-auth accept files from anyone
|
||||
-n, --no-auth accept files and fetches from anyone
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-w seconds sender timeout before giving up
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -554,27 +740,30 @@ optional arguments:
|
||||
<h3>The rnx Utility<a class="headerlink" href="#the-rnx-utility" title="Permalink to this heading">#</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnx</span></code> utility is a basic remote command execution program. It allows you to
|
||||
execute commands on remote systems over Reticulum, and to view returned command
|
||||
output.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnx on the listening system, specifying which identities
|
||||
# are allowed to execute commands
|
||||
rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
|
||||
# From another system, run a command
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
|
||||
# Or enter the interactive mode pseudo-shell
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 -x
|
||||
|
||||
# The default identity file is stored in
|
||||
# ~/.reticulum/identities/rnx, but you can use
|
||||
# another one, which will be created if it does
|
||||
# not already exist
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 -i /path/to/identity -x
|
||||
output. For a fully interactive remote shell solution, be sure to also take a look
|
||||
at the <a class="reference external" href="https://github.com/acehoss/rnsh">rnsh</a> program.</p>
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Run rnx on the listening system, specifying which identities are allowed to execute commands:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can specify as many allowed senders as needed, or completely disable authentication.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
|
||||
[destination] [command]
|
||||
<p>From another system, run a command on the remote:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Or enter the interactive mode pseudo-shell:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnx 7a55144adf826958a9529a3bcf08b149 -x
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The default identity file is stored in <code class="docutils literal notranslate"><span class="pre">~/.reticulum/identities/rnx</span></code>, but you can use
|
||||
another one, which will be created if it does not already exist</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnx 7a55144adf826958a9529a3bcf08b149 -i /path/to/identity -x
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-n] [-N]
|
||||
[-d] [-m] [-a allowed_hash] [-w seconds] [-W seconds] [--stdin STDIN]
|
||||
[--stdout STDOUT] [--stderr STDERR] [--version] [destination] [command]
|
||||
|
||||
Reticulum Remote Execution Utility
|
||||
|
||||
@@ -610,9 +799,16 @@ optional arguments:
|
||||
<h3>The rnodeconf Utility<a class="headerlink" href="#the-rnodeconf-utility" title="Permalink to this heading">#</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> utility allows you to inspect and configure existing <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNodes</span></a>, and
|
||||
to create and provision new <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNodes</span></a> from any supported hardware devices.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-C] [-N] [-T] [-b] [-B] [-p] [--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [--version] [port]
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
|
||||
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
|
||||
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
|
||||
[--trust-key hexbytes] [--version] [port]
|
||||
|
||||
RNode Configuration and firmware utility. This program allows you to change various settings and startup modes of RNode. It can also install, flash and update the firmware on supported devices.
|
||||
RNode Configuration and firmware utility. This program allows you to change various
|
||||
settings and startup modes of RNode. It can also install, flash and update the firmware
|
||||
on supported devices.
|
||||
|
||||
positional arguments:
|
||||
port serial port where RNode is attached
|
||||
@@ -628,11 +824,14 @@ options:
|
||||
-e, --extract Extract firmware from connected RNode for later use
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or update
|
||||
-C, --clear-cache Clear locally cached firmware files
|
||||
--baud-flash baud_flash
|
||||
Set specific baud rate when flashing device. Default is 921600
|
||||
-N, --normal Switch device to normal mode
|
||||
-T, --tnc Switch device to TNC mode
|
||||
-b, --bluetooth-on Turn device bluetooth on
|
||||
-B, --bluetooth-off Turn device bluetooth off
|
||||
-p, --bluetooth-pair Put device into bluetooth pairing mode
|
||||
-D i, --display i Set display intensity (0-255)
|
||||
--freq Hz Frequency in Hz for TNC mode
|
||||
--bw Hz Bandwidth in Hz for TNC mode
|
||||
--txp dBm TX power in dBm for TNC mode
|
||||
@@ -641,6 +840,8 @@ options:
|
||||
--eeprom-backup Backup EEPROM to file
|
||||
--eeprom-dump Dump EEPROM to console
|
||||
--eeprom-wipe Unlock and wipe EEPROM
|
||||
-P, --public Display public part of signing key
|
||||
--trust-key hexbytes Public key to trust for device verification
|
||||
--version Print program version and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -685,6 +886,8 @@ assignment varies from one boot to another.</p>
|
||||
<span id="using-systemd"></span><h3>Reticulum as a System Service<a class="headerlink" href="#reticulum-as-a-system-service" title="Permalink to this heading">#</a></h3>
|
||||
<p>Instead of starting Reticulum manually, you can install <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as a system
|
||||
service and have it start automatically at boot.</p>
|
||||
<section id="systemwide-service">
|
||||
<h4>Systemwide Service<a class="headerlink" href="#systemwide-service" title="Permalink to this heading">#</a></h4>
|
||||
<p>If you installed Reticulum with <code class="docutils literal notranslate"><span class="pre">pip</span></code>, the <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> program will most likely
|
||||
be located in a user-local installation path only, which means <code class="docutils literal notranslate"><span class="pre">systemd</span></code> will not
|
||||
be able to execute it. In this case, you can simply symlink the <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> program
|
||||
@@ -725,6 +928,43 @@ WantedBy=multi-user.target
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="userspace-service">
|
||||
<h4>Userspace Service<a class="headerlink" href="#userspace-service" title="Permalink to this heading">#</a></h4>
|
||||
<p>Alternatively you can use a user systemd service instead of a system wide one. This way the whole setup can be done as a regular user.
|
||||
Create a user systemd service files <code class="docutils literal notranslate"><span class="pre">~/.config/systemd/user/rnsd.service</span></code> with the following content:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[Unit]
|
||||
Description=Reticulum Network Stack Daemon
|
||||
After=default.target
|
||||
|
||||
[Service]
|
||||
# If you run Reticulum on WiFi devices,
|
||||
# or other devices that need some extra
|
||||
# time to initialise, you might want to
|
||||
# add a short delay before Reticulum is
|
||||
# started by systemd:
|
||||
# ExecStartPre=/bin/sleep 10
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ExecStart=RNS_BIN_DIR/rnsd --service
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Replace <code class="docutils literal notranslate"><span class="pre">RNS_BIN_DIR</span></code> with the path to your Reticulum binary directory (eg. /home/USERNAMEHERE/rns/bin).</p>
|
||||
<p>Start user service:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>systemctl --user daemon-reload
|
||||
systemctl --user start rnsd.service
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you want to automatically start <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> without having to log in as the USERNAMEHERE, do:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo loginctl enable-linger USERNAMEHERE
|
||||
systemctl --user enable rnsd.service
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -757,7 +997,7 @@ WantedBy=multi-user.target
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -789,6 +1029,7 @@ WantedBy=multi-user.target
|
||||
<li><a class="reference internal" href="#included-utility-programs">Included Utility Programs</a><ul>
|
||||
<li><a class="reference internal" href="#the-rnsd-utility">The rnsd Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnstatus-utility">The rnstatus Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnid-utility">The rnid Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnpath-utility">The rnpath Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnprobe-utility">The rnprobe Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rncp-utility">The rncp Utility</a></li>
|
||||
@@ -798,7 +1039,11 @@ WantedBy=multi-user.target
|
||||
</li>
|
||||
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
|
||||
<li><a class="reference internal" href="#fixed-serial-port-names">Fixed Serial Port Names</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a><ul>
|
||||
<li><a class="reference internal" href="#systemwide-service">Systemwide Service</a></li>
|
||||
<li><a class="reference internal" href="#userspace-service">Userspace Service</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -812,14 +1057,11 @@ WantedBy=multi-user.target
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=e8582e68"></script>
|
||||
<script src="_static/doctools.js?v=888ff710"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=4825356b"></script>
|
||||
<script src="_static/scripts/furo.js?v=2c7c1115"></script>
|
||||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||||
<script src="_static/copybutton.js?v=f281be69"></script>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user