Compare commits

...

415 Commits

Author SHA1 Message Date
Mark Qvist 30248854ed Updated changelog 2024-10-11 17:13:03 +02:00
Mark Qvist f34bc75588 Updated docs 2024-10-11 16:47:53 +02:00
Mark Qvist 3b23e2f37d Improved RNode BLE reconnection reliability 2024-10-11 13:38:16 +02:00
Mark Qvist 7417cf5947 Add rnode battery state to rnstatus output 2024-10-11 10:14:10 +02:00
Mark Qvist 60d8da843c Disable tty module dependency for rnx, since it is currently unused 2024-10-11 09:54:09 +02:00
Mark Qvist f9667fd684 Fixed missing import on Android 2024-10-10 23:49:20 +02:00
Mark Qvist d9269c6047 Updated version 2024-10-10 23:32:09 +02:00
Mark Qvist 6521f839cd Fixed resource transfers hanging for a long time over slow links if proof packet is lost 2024-10-10 17:06:43 +02:00
Mark Qvist d63bbcdc0a Updated changelog 2024-10-10 00:45:09 +02:00
Mark Qvist c36c7186de Updated docs 2024-10-10 00:44:33 +02:00
Mark Qvist 6fec76205c Added save directory option to rncp 2024-10-10 00:41:57 +02:00
Mark Qvist 715f4d9fcb Updated version 2024-10-09 20:03:05 +02:00
Mark Qvist 8d7857c4e2 Fixed rncp fstrings for Android build 2024-10-09 19:53:07 +02:00
Mark Qvist c9a2b45368 Added physical layer transfer rate output option to rncp 2024-10-09 19:39:39 +02:00
Mark Qvist c57d927660 Cleanup 2024-10-09 19:38:46 +02:00
Mark Qvist 8d98c8751a Fixed resource progress calculation bug. Actually fixes #522. 2024-10-09 19:38:25 +02:00
Mark Qvist 527f6cc906 Fuxed typo 2024-10-07 22:10:17 +02:00
Mark Qvist a0d61f6441 Added error descriptions for modem communication timeout 2024-10-07 20:55:34 +02:00
Mark Qvist c5687f190b Updated manual 2024-10-06 10:49:56 +02:00
Mark Qvist 44d1f6d0e5 Updated changelog 2024-10-06 10:49:48 +02:00
Mark Qvist ac09bc3567 Updated manual 2024-10-06 10:28:26 +02:00
Mark Qvist a41bce012b Fix docs images for PDF generation 2024-10-06 10:27:27 +02:00
Mark Qvist 83a2999d29 Revert AF_INET6 addition to TCPInterface, since it breaks normal IPv4 connectivity for interface 2024-10-06 10:01:55 +02:00
markqvist 4465fa9882 Merge pull request #545 from deavmi/master
Support IPv6 for outbound TCP interface (TCPClientInterface)
2024-10-05 23:46:28 +02:00
Mark Qvist ce974db084 Merge branch 'master' of github.com:markqvist/Reticulum 2024-10-05 23:45:48 +02:00
markqvist e6c1dc075b Merge pull request #556 from jacobeva/rnode-multi-fix
Fix interface values not being set on RNodeSubInterface instances
2024-10-05 23:45:21 +02:00
Mark Qvist 9602f67b06 Merge branch 'master' of github.com:markqvist/Reticulum 2024-10-05 23:44:17 +02:00
markqvist ef798e0d54 Merge pull request #543 from jacobeva/display-fix
Allow for use of display by master on NRF52
2024-10-05 23:43:56 +02:00
Mark Qvist 5cd8d229fb Updated manual 2024-10-05 23:43:28 +02:00
Mark Qvist d4808b7ff1 Added supported boards to manual 2024-10-05 23:43:02 +02:00
markqvist 3dc8729e70 Merge pull request #565 from jacobeva/framing-fix
Fix RNodeMultiInterface interface framing
2024-10-05 23:03:36 +02:00
markqvist f500a063dc Merge pull request #564 from prusnak/docs-hardware
docs: add Heltec LoRa32 v3.0 and LilyGO LoRa32 v1.0 to hardware
2024-10-05 23:00:43 +02:00
Mark Qvist eca1e53b55 Added support for T-Beam Supreme, T-Deck and T3S3 devices with SX127X chips to rnodeconf 2024-10-05 22:29:31 +02:00
Mark Qvist 53226d7035 Cap resource max window for resource transfer over very slow links 2024-10-05 20:54:42 +02:00
Mark Qvist 7363c9c821 Increase PATH_REQUEST_RG to 1.5 seconds 2024-10-05 19:20:48 +02:00
Mark Qvist bb8b8b4f81 Added handling for receiving a link proof after the link had timed out and been closed, but before it having been purged from active links table 2024-10-05 18:43:56 +02:00
Mark Qvist 0f0f459321 Updated version 2024-10-05 17:05:41 +02:00
Mark Qvist df887f6d63 Added product and model code defines for new boards to rnodeconf 2024-10-05 17:05:34 +02:00
Mark Qvist b526e3554c Added low memory error decsription to RNodeInterface 2024-10-05 17:05:02 +02:00
Mark Qvist 903ab53fc9 Fixed init fail due to missing library on Android/Termux 2024-10-05 17:04:39 +02:00
Mark Qvist f461a7827b Added T-Deck defines to rnodeconf 2024-10-03 00:52:38 +02:00
Mark Qvist 62091b28b0 Fixed version comparison 2024-10-02 02:54:18 +02:00
Mark Qvist 48045856bf Updated changelog 2024-10-02 02:09:41 +02:00
Mark Qvist 6ba5efcb42 Updated documentation 2024-10-02 02:08:41 +02:00
Mark Qvist a505441b98 Added BLE connection config to docs 2024-10-02 02:05:00 +02:00
Mark Qvist 976e5543e1 Updated changelog 2024-10-02 01:58:35 +02:00
Mark Qvist fcc7b50ac6 Updated docs 2024-10-01 23:53:53 +02:00
Mark Qvist 72971d1aef Handle RNode BLE MTU request errors 2024-10-01 23:52:04 +02:00
Mark Qvist 9a8d46ab21 Updated version 2024-10-01 17:28:40 +02:00
Mark Qvist 8adab7ee7d Added BLE support to Android RNodeInterface 2024-10-01 17:27:45 +02:00
Mark Qvist b5bde99322 Added RNode battery info to rnstatus output 2024-10-01 17:25:44 +02:00
Mark Qvist 560c8e164c Added BLE support to RNodeInterface 2024-10-01 17:25:16 +02:00
jacob.eva e059363f1d Version bump for CE firmware version which will contain framing change 2024-10-01 16:02:07 +01:00
jacob.eva 4930477b99 Fix interface framing assignment conflict 2024-10-01 15:58:27 +01:00
Mark Qvist 312489e4dc Added BLE config support to RNodeInterface 2024-09-30 19:09:35 +02:00
Pavol Rusnak 43d8fdb423 docs: add Heltec LoRa32 v3.0 and LilyGO LoRa32 v1.0 to hardware 2024-09-29 11:51:43 +02:00
Mark Qvist 1c56385473 Added display blanking timeout configuration to rnodeconf 2024-09-29 02:35:44 +02:00
Mark Qvist 787af92ade Added option to configure NeoPixel intensity to rnodeconf 2024-09-27 20:07:04 +02:00
Mark Qvist 131dbd2813 Updated changelog 2024-09-25 13:26:23 +02:00
Mark Qvist 9df81ce365 Updated manual 2024-09-25 13:25:43 +02:00
Mark Qvist 490a56450a Updated changelog 2024-09-25 13:23:15 +02:00
Mark Qvist 52a5156304 Cleanup 2024-09-25 13:20:41 +02:00
Mark Qvist 538e7320fd Updated docs 2024-09-25 13:17:03 +02:00
Mark Qvist 2d351a59e9 Updated version 2024-09-25 13:11:17 +02:00
Mark Qvist 2269d6cef9 Updated readme 2024-09-25 13:06:31 +02:00
Mark Qvist 813edc8b17 Updated readme 2024-09-25 13:04:23 +02:00
Mark Qvist 099e344996 Updated roadmap 2024-09-25 12:43:40 +02:00
Mark Qvist 42319a092d Added additional information to interface stats 2024-09-24 20:26:15 +02:00
Mark Qvist cdee3b6191 Updated changelog 2024-09-24 10:12:51 +02:00
Mark Qvist e41d8ff296 Updated docs 2024-09-24 10:09:37 +02:00
Mark Qvist 946bea8825 Update version 2024-09-22 11:43:35 +02:00
Mark Qvist ba856ea1c4 Handle link transport edge case 2024-09-21 19:04:28 +02:00
jacob.eva 9a97195b8c Fix interface values not being set on RNodeSubInterface instances 2024-09-20 17:50:34 +01:00
Mark Qvist 3e4172b697 Updated changelog 2024-09-18 23:40:38 +02:00
Mark Qvist 66163776c2 Updated changelog 2024-09-18 23:32:45 +02:00
Mark Qvist 3dbde726c1 Updated manual 2024-09-18 23:31:27 +02:00
Mark Qvist 97ae4d74b3 Updated docs 2024-09-17 14:56:22 +02:00
Mark Qvist c71ece6b8e Updated version 2024-09-16 20:11:12 +02:00
Mark Qvist 1e45a002e1 Merge branch 'master' of github.com:markqvist/Reticulum 2024-09-16 20:10:55 +02:00
markqvist 68e64523b5 Merge pull request #552 from liamcottle/fix/ax25-kiss-interface
fix KISSInterface is not defined error for AX25KISSInterface
2024-09-16 19:56:13 +02:00
Mark Qvist d9e6145034 Raise exception when SINGLE destination is created without identity 2024-09-16 18:20:53 +02:00
Mark Qvist a91e67129e Update profiler output 2024-09-16 18:20:31 +02:00
liamcottle 76362bad4a fix KISSInterface is not defined error for AX25KISSInterface 2024-09-16 14:27:08 +12:00
Mark Qvist 421b5ef32e Recursive profiler results output 2024-09-15 16:46:52 +02:00
Mark Qvist 8d61ee8a81 Added performance profiler 2024-09-15 15:12:53 +02:00
Mark Qvist 2329181c88 Prioritize interfaces according to bitrate 2024-09-15 14:14:00 +02:00
markqvist 8ea0dc65c4 Merge pull request #551 from jacobeva/opencom_xl
Add support for openCom XL
2024-09-14 23:44:48 +02:00
jacob.eva bba67836f0 Add support for openCom XL 2024-09-13 11:30:54 +01:00
Mark Qvist a666bb6e73 Added minimum link traffic timeout 2024-09-12 17:52:40 +02:00
Mark Qvist 7b7ebbec90 Updated roadmap 2024-09-09 15:13:21 +02:00
Mark Qvist 8b3523dee0 Updated changelog 2024-09-09 15:09:42 +02:00
Mark Qvist 2901ed2bae Updated changelog 2024-09-09 15:09:07 +02:00
Mark Qvist 34010c94d1 Updated manual 2024-09-09 15:08:46 +02:00
Mark Qvist a4b5248a4c Cleanup 2024-09-09 14:48:58 +02:00
Mark Qvist 75272d77a5 Cleanup 2024-09-09 14:47:28 +02:00
Mark Qvist d4ad4589dd Cleanup 2024-09-09 14:46:58 +02:00
Mark Qvist 8d45ad36eb Cleanup 2024-09-09 14:46:32 +02:00
Mark Qvist 2a0d411869 Cleanup 2024-09-09 14:45:08 +02:00
Mark Qvist b9421347ef Cleanup 2024-09-09 14:43:50 +02:00
markqvist ffec78d49a Merge pull request #544 from deavmi/deavmi-patch-1
Add a pinch of CI/CD (no CD yet)
2024-09-09 14:42:30 +02:00
Mark Qvist 356ae378f9 Cleanup 2024-09-09 14:32:07 +02:00
Mark Qvist 28e3919dbd T-Echo product and model codes 2024-09-09 14:30:06 +02:00
markqvist 58a19610c4 Merge pull request #541 from jeremybox/t-echo
Add support for TECHO device
2024-09-09 14:18:15 +02:00
Mark Qvist 50b1eae380 File move fix for windows 2024-09-09 02:11:46 +02:00
Mark Qvist c119ef4273 Standardised ratchet id getter 2024-09-08 20:33:35 +02:00
Mark Qvist b506ca94d0 Updated documentation and manual 2024-09-08 17:56:02 +02:00
Mark Qvist a072a5b074 Added automatic ratchet reload if required ratchet is unavailable 2024-09-08 17:48:25 +02:00
Mark Qvist 3a580e74de Make ratchet IDs available to applications 2024-09-08 14:55:07 +02:00
jeremy 9a20a3929a correct t-echo model 2024-09-07 19:17:06 -04:00
Mark Qvist fe054fd03c Added destination ratchet ID getter to API 2024-09-07 22:32:03 +02:00
Mark Qvist 4524a17e67 Updated documentation 2024-09-06 19:52:11 +02:00
Mark Qvist 8a82d6bfeb Allow announce handlers to also receive path responses 2024-09-06 19:52:05 +02:00
Mark Qvist 971f5ffadd Check ratchet dir exists before cleaning 2024-09-05 15:58:54 +02:00
Mark Qvist 6a392fdb0f Updated readme 2024-09-05 15:21:45 +02:00
Mark Qvist b42e075be0 Updated manual and documentation 2024-09-05 15:17:58 +02:00
Mark Qvist 4bc8a0b69b Updated manual and documentation 2024-09-05 15:16:09 +02:00
Mark Qvist 9ef10a7b3e Expanded and documented ratchet API 2024-09-05 15:02:22 +02:00
Mark Qvist 320704f812 Updated documentation 2024-09-05 14:58:06 +02:00
Mark Qvist c5e5986b89 Updated documentation 2024-09-05 12:58:35 +02:00
Tristan Brice Velloza Kildaire 5c6ee07d66 TCPInterface
- When connect(s, Bool)` is called construct a socket that supports both address families
2024-09-05 00:07:35 +02:00
Tristan Brice Velloza Kildaire 3eb8d92028 Rename 2024-09-04 23:59:03 +02:00
Tristan Brice Velloza Kildaire ef3baf2cd9 Add bade
(Will work once active on mark's repo)
2024-09-04 23:58:16 +02:00
Tristan Brice Velloza Kildaire f2f936d846 Clean up testing 2024-09-04 23:56:55 +02:00
Tristan Brice Velloza Kildaire 6599e210de Fixed up test 2024-09-04 23:56:01 +02:00
Mark Qvist d21dda2830 Set context flags on path response 2024-09-04 19:39:59 +02:00
Mark Qvist 6ac393bbcd Updated ratchet count 2024-09-04 19:33:04 +02:00
Mark Qvist 0c04663942 Merge branch 'master' of github.com:markqvist/Reticulum 2024-09-04 19:08:26 +02:00
Mark Qvist bfa216de54 Cleanup 2024-09-04 19:08:18 +02:00
markqvist a4b1606921 Merge pull request #542 from jacobeva/master
Remove match and therefore dependency on Python 3.10
2024-09-04 19:01:08 +02:00
Mark Qvist ad0db9c95c Updated version 2024-09-04 17:47:26 +02:00
Mark Qvist 2fdcbec860 Updated documentation 2024-09-04 17:40:02 +02:00
Mark Qvist dd889d16d4 Added ratchets example 2024-09-04 17:37:34 +02:00
Mark Qvist a11f14e75f Implemented ratchets 2024-09-04 17:37:18 +02:00
Mark Qvist c32086c6f1 Updated documentation 2024-09-04 17:00:11 +02:00
jacob.eva 9d744e2317 Allow for display use by master on NRF52 on Android 2024-09-04 11:54:32 +01:00
jacob.eva d64064691a Allow for use of display by master on NRF52 2024-09-04 11:52:41 +01:00
Mark Qvist 54eaff203f Implemented ratchet generation, rotation and persistence 2024-09-04 12:02:55 +02:00
Mark Qvist 2bf75f60bc Cleanup 2024-09-04 11:23:08 +02:00
Mark Qvist 3f64141455 Fixed missing establishment_rate property init on Link 2024-09-04 10:32:54 +02:00
jeremy b4ac3df2d0 remove t-echo menu items 2024-09-03 17:24:11 -04:00
jeremy 8193f3621c remove symlink 2024-09-03 17:17:17 -04:00
jeremybox 5166596375 Update RNodeInterface.py
reverts extra debugging message detail
2024-09-03 17:14:07 -04:00
jacob.eva 063ea2bb7a Remove match and therefore dependency on Python 3.10 2024-09-03 22:12:25 +01:00
jeremy 625db2622d Pushing changes to branch 2024-09-03 17:09:59 -04:00
Tristan B. Velloza Kildaire a8bc468e21 Update python-app.yml 2024-09-03 18:53:11 +02:00
Tristan B. Velloza Kildaire 95c4269869 Create python-app.yml 2024-09-03 18:52:10 +02:00
jeremy 65a40aefb6 trying to get techo working 2024-09-03 01:57:07 -04:00
jeremy a840bd4aaf changes needed to support the t-echo device 2024-08-31 23:39:36 -04:00
Mark Qvist 7f2154110c Cleanup 2024-08-30 13:33:51 +02:00
Mark Qvist 9bc957e442 Updated documentation 2024-08-29 23:46:10 +02:00
Mark Qvist 6d5ef3a511 Cleanup 2024-08-29 23:45:16 +02:00
Mark Qvist dec9145d65 Updated manual and documentation 2024-08-29 17:02:22 +02:00
Mark Qvist b3536f16e8 Added remote management config options to example config 2024-08-29 16:50:05 +02:00
Mark Qvist 4e21b6f3b9 Updated changelog 2024-08-29 16:29:58 +02:00
Mark Qvist 31e0939657 Updated manual 2024-08-29 15:41:16 +02:00
Mark Qvist bd9aa2954b Improved resource transfer performance for segmented files 2024-08-29 15:26:53 +02:00
Mark Qvist 3a5ee15dd8 Cleanup 2024-08-29 15:25:37 +02:00
Mark Qvist 166b00b6bf Updated documentation 2024-08-29 15:25:12 +02:00
Mark Qvist 2413add00d Cleanup 2024-08-29 14:54:40 +02:00
Mark Qvist 169d1921be Added JSON output to rnpath utility 2024-08-29 14:51:38 +02:00
Mark Qvist 7be6a0e000 Fixed exit code 2024-08-29 13:20:00 +02:00
Mark Qvist d3b8c1c829 Added path and rate tables to remote management 2024-08-29 13:19:39 +02:00
Mark Qvist 8ee11ac32c Added request concluded status to Link API 2024-08-29 13:14:55 +02:00
Mark Qvist cf87b1352a Added max hops filter to rnpath table output 2024-08-29 11:17:07 +02:00
Mark Qvist 219d717afb Added timeout argument to rnstatus remote queries 2024-08-29 09:35:33 +02:00
Mark Qvist e8d1897edd Added remote transport instance status to rnstatus utility 2024-08-29 01:54:34 +02:00
Mark Qvist bce37fe8c0 Fixed rnstatus JSON output bug when IFAC was enabled on an interface 2024-08-28 23:25:18 +02:00
Mark Qvist 0c95d720db Improved rncp progress status display 2024-08-28 21:34:16 +02:00
Mark Qvist 96527380c3 Improved rncp progress status display 2024-08-28 21:21:38 +02:00
Mark Qvist 035a44e34d Fixed invalid resource progress reported in some cases 2024-08-28 21:21:09 +02:00
Mark Qvist 59bb09426c Updated documentation 2024-08-28 20:37:19 +02:00
Mark Qvist 6ac07989b0 Added link age to link API 2024-08-28 20:36:51 +02:00
Mark Qvist f1d6cda337 Added RNodeMultiInterface to documentation 2024-08-28 18:47:33 +02:00
Mark Qvist 4aa60243a7 Merge branch 'master' of github.com:markqvist/Reticulum 2024-08-28 18:27:01 +02:00
markqvist eb4fc3362a Merge pull request #530 from jacobeva/master
Add RNodeMultiInterface support
2024-08-28 18:26:32 +02:00
Mark Qvist 849bd1bdad Fixed typo 2024-08-28 16:54:31 +02:00
markqvist cdce0c4223 Merge pull request #534 from faragher/master
Migrating BtB Server
2024-08-28 16:36:36 +02:00
faragher 4e16e6ac0e Server Migration 2024-08-27 14:07:25 -05:00
faragher 9e054ae71d Server Migration 2024-08-27 14:06:34 -05:00
faragher 2fad5464da Server Migration 2024-08-27 14:04:54 -05:00
jacob.eva 3c4783b25e Merge branch 'master' of https://github.com/markqvist/Reticulum 2024-08-19 08:29:16 +01:00
jacob.eva 5feb833573 Add RNodeMultiInterface 2024-08-19 08:19:42 +01:00
jacob.eva 60e6b712d2 Update minimum SF 2024-08-19 08:19:32 +01:00
Mark Qvist a1be97bd69 Merge branch 'master' of github.com:markqvist/Reticulum 2024-08-17 16:07:41 +02:00
Mark Qvist 07ff9fc663 Updated readme 2024-08-17 16:07:20 +02:00
markqvist 2ef87a5e70 Merge pull request #512 from attermann/master
Fix for broken `--rom` manual device provisioning
2024-08-17 14:42:06 +02:00
Mark Qvist e3948526fe Cleanup 2024-08-17 14:38:07 +02:00
markqvist 2943d59042 Merge pull request #516 from jschulthess/master
Link example - Allow server to gracefully exit
2024-08-17 14:35:18 +02:00
markqvist 1335ffd528 Merge pull request #521 from nathmo/nathmo-patch-egraceful_xit
fixed small typo : egraceful_xit()
2024-08-17 14:33:51 +02:00
Nathann Morand 4e783ced31 fixed small typo egraceful_xit()
typo in Reticulum/RNS/Utilities/rnodeconf.py (egraceful_xit())
that cause a crash if we run rnodeconf -i on an upprovisionned node
2024-07-20 13:54:43 +02:00
Jürg Schulthess 228667578e Allow server to gracefully exit 2024-06-21 17:01:56 +02:00
Mark Qvist 6ded42edd7 Updated readme 2024-06-05 00:36:34 +02:00
Mark Qvist d1a150329a Updated documentation 2024-06-02 13:32:59 +02:00
Mark Qvist 893dc2877c Updated readme 2024-06-02 09:52:21 +02:00
Mark Qvist 86224ef387 Updated documentation 2024-06-02 09:42:43 +02:00
Mark Qvist 794cac98fe Updated readme 2024-06-02 08:43:30 +02:00
Mark Qvist cfdba51640 Merge branch 'master' of github.com:markqvist/Reticulum 2024-06-02 08:42:31 +02:00
Mark Qvist c4ecbf29cb Updated docs 2024-06-02 08:39:38 +02:00
Mark Qvist c80289987c Updated readme 2024-06-02 08:39:21 +02:00
Mark Qvist 9371f857a8 Updated documentation 2024-06-01 16:32:58 +02:00
markqvist 4fdb9dda40 Merge pull request #509 from liamcottle/master
Fix for macos failing to set firmware hash on NRF52
2024-05-31 13:47:01 +02:00
liamcottle c4705fd594 check platform is macos before delaying nrf52 reset 2024-05-31 13:12:39 +12:00
Mark Qvist 30228d12f7 Updated readme 2024-05-29 19:09:43 +02:00
Chad Attermann 1cee0a2619 Fix for broken --rom manual device provisioning
Initializes `selected_model` with the value of model specified on the
command line.
2024-05-29 09:04:14 -06:00
liamcottle df92fb1bcf fix for macOS failing to set firmware hash on NRF52 when resetting too quickly 2024-05-29 11:39:13 +12:00
Mark Qvist 3a163c6f09 Added fetch request jail option to rncp 2024-05-28 20:58:20 +02:00
Mark Qvist 1f6560619e Added link table stats to rnstatus 2024-05-26 01:28:40 +02:00
Mark Qvist b994db3745 Updated command line option description 2024-05-25 22:39:50 +02:00
Mark Qvist 173a534572 Updated version 2024-05-25 22:38:25 +02:00
Mark Qvist fc7268a8ff Added switch for allowing file fetch to rncp utility 2024-05-25 22:37:50 +02:00
Mark Qvist 0049c98684 Added comment about path resolution 2024-05-22 12:41:38 +02:00
Mark Qvist 3ef6c06b51 Fixed incorrect TX power limit on Android 2024-05-22 12:40:21 +02:00
Mark Qvist 0bb1108771 Mark path unresponsive when link establishment fails due to potential interface-local destination roaming 2024-05-19 12:35:38 +02:00
Mark Qvist ba2feaa211 Updated changelog 2024-05-18 18:51:17 +02:00
Mark Qvist 097d2b0dd9 Updated changelog 2024-05-18 18:48:32 +02:00
Mark Qvist bb0ce4faca Added T3S3 flashing, fixed Heltec V3 autoinstaller menu 2024-05-18 18:40:21 +02:00
Mark Qvist 5915228f5b Updated documentation 2024-05-18 18:38:06 +02:00
Mark Qvist 0b66649158 Avoid nRF52 hard reset after EEPROM wipe 2024-05-18 00:18:54 +02:00
markqvist e28dd6e14a Merge pull request #502 from jacobeva/master
Extend RAK4631 support
2024-05-18 00:15:48 +02:00
markqvist 0a15b4c6c1 Merge branch 'master' into master 2024-05-18 00:15:13 +02:00
markqvist 62db09571d Merge pull request #504 from jacobeva/hash-feature
Add ability to get target and calculated firmware hash from device
2024-05-18 00:04:24 +02:00
Mark Qvist 444ae0206b Added better handling on Windows of interfaces that are non-adoptable for AutoInterface 2024-05-17 23:54:48 +02:00
Mark Qvist 4b07e30b9d Updated version 2024-05-17 23:54:04 +02:00
markqvist 583e65419e Merge pull request #506 from liamcottle/feature/windows-auto-interface
Implement AutoInterface support on Windows
2024-05-17 16:32:33 +02:00
liamcottle 1564930a51 auto interface working on windows 2024-05-17 04:09:11 +12:00
markqvist b81b1de4eb Merge pull request #500 from faragher/master
Windows DTR timing fix
2024-05-15 20:06:36 +02:00
jacob.eva 746a38f818 Add ability to get target and calculated firmware hash from device 2024-05-13 22:55:49 +01:00
jacob.eva c230eceaa6 Extend RAK4631 support 2024-05-13 21:49:57 +01:00
Mark Qvist 09d9285104 Allow recursive path resolution for clients on roaming-mode interfaces 2024-05-12 12:31:51 +02:00
faragher 3551662187 Changing log levels 2024-05-08 02:19:59 -05:00
faragher f7f34e0ea3 Windows DTR timing adjustments 2024-05-08 02:14:29 -05:00
Mark Qvist 43fc2a6c92 Updated changelog 2024-05-05 20:05:30 +02:00
Mark Qvist b17175dfef Updated changelog 2024-05-05 19:57:48 +02:00
Mark Qvist 1103784997 Updated documentation 2024-05-05 19:56:33 +02:00
Mark Qvist d2feb8b136 Improved path response logic 2024-05-04 21:57:03 +02:00
Mark Qvist f595648a9b Updated tests 2024-05-04 20:27:27 +02:00
Mark Qvist b06f5285c5 Fix LR proof delivery on unknown hop count paths 2024-05-04 20:27:04 +02:00
Mark Qvist 8330f70a27 Fixed link packet routing in topologies where transport packets leak to non-intended instances in the link chain 2024-05-04 19:52:02 +02:00
Mark Qvist 15e10b9435 Added expected hops property to link class 2024-05-04 19:15:57 +02:00
Mark Qvist b91c852330 Updated path request timing 2024-05-04 16:19:04 +02:00
Mark Qvist 75acdf5902 Updated version 2024-05-03 23:49:39 +02:00
Mark Qvist dae40f2684 Removed T3S3 build from autoinstaller 2024-05-03 18:20:17 +02:00
Mark Qvist 4edacf82f3 Merge branch 'master' of github.com:markqvist/Reticulum 2024-05-03 16:22:37 +02:00
markqvist 4b0a0668a5 Update Contributing.md 2024-05-01 17:50:15 +02:00
markqvist a52af17123 Merge pull request #495 from jschulthess/master
optionally load identity file from file in Echo and Link examples
2024-05-01 17:28:10 +02:00
Mark Qvist 0b0a3313c5 Multicast address type modifications 2024-05-01 15:49:48 +02:00
markqvist 34af2e7af7 Merge pull request #476 from thiaguetz/feat/multicast-address-type
feat: implement multicast address type definition on AutoInterface configuration
2024-05-01 15:44:03 +02:00
Jürg Schulthess 12bf7977d2 fix comment 2024-04-29 08:25:40 +02:00
Jürg Schulthess b69b939d6f realign with upstream 2024-04-29 08:10:48 +02:00
Jürg Schulthess b5556f664b realign with upstream 2024-04-29 08:07:22 +02:00
Jürg Schulthess f804ba0263 explicit exit not needed 2024-04-29 08:04:04 +02:00
Jürg Schulthess 84a1ab0ca3 add option to load identity from file 2024-04-29 07:59:55 +02:00
markqvist 465695b9ae Merge pull request #490 from nothingbutlucas/master
docs: Fix a typo. startig / starting
2024-04-22 01:33:10 +02:00
Mark Qvist a999a4a250 Added support for T3S3 boards to rnodeconf autoinstaller 2024-04-22 01:26:35 +02:00
nothingbutlucas cbb5d99280 docs: Fix a typo. startig / starting
Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-21 16:11:03 -03:00
Mark Qvist 64f5192c79 Changed rnodeconf autoinstaller menu order 2024-04-20 22:25:57 +02:00
Mark Qvist d223ebc8c0 Added rnodeconf autoinstaller support for Heltec LoRa32 V3 boards 2024-04-20 22:03:14 +02:00
markqvist c28f413fe6 Merge pull request #486 from cobraPA/upstream_add_heltec_v3
Add product and model, plus support for Heltec V3 serial only setup to rnodeconf.
2024-04-20 18:54:09 +02:00
Kevin Brosius 92e5f65887 Add product and model, plus support for Heltec V3 serial only setup
to rnodeconf.
2024-04-11 01:41:50 -04:00
Mark Qvist b977f33df6 Display error on unknown model capabilities instead of fail 2024-03-28 12:05:30 +01:00
Mark Qvist 589fcb8201 Added custom EEPROM bootstrap to rnodeconf 2024-03-28 00:04:48 +01:00
Mark Qvist e5427d70ac Added custom EEPROM bootstrap to rnodeconf 2024-03-27 21:48:32 +01:00
Mark Qvist 2f5381b307 Added TCXO model code comment 2024-03-24 11:51:44 +01:00
Thiaguetz 11baace08d feat: implement multicast address type definition on AutoInterface configuration 2024-03-23 00:54:56 -03:00
Mark Qvist a4d5b5cb17 Merge branch 'master' of github.com:markqvist/Reticulum 2024-03-19 11:52:58 +01:00
Mark Qvist 9cb181690e Added link getter to resource advertisement class 2024-03-19 11:52:32 +01:00
markqvist ff6604290e Update LICENSE 2024-03-10 22:14:29 +01:00
markqvist 2dbd3cbc0f Update Contributing.md 2024-03-10 22:14:03 +01:00
markqvist 2a11097cac Update Contributing.md 2024-03-10 22:13:33 +01:00
markqvist c0e3181ae3 Update Contributing.md 2024-03-10 22:11:44 +01:00
markqvist 5a0316ae7f Update Contributing.md 2024-03-10 20:39:49 +01:00
Mark Qvist 177bb62610 Updated changelog 2024-03-09 21:09:06 +01:00
Mark Qvist 7cd3cde398 Updated changelog 2024-03-09 21:08:17 +01:00
Mark Qvist 29bdcea616 Updated manual 2024-03-09 21:05:59 +01:00
Mark Qvist d9460c43ad Updated version 2024-03-09 21:01:12 +01:00
markqvist fb02e980db Merge pull request #461 from attermann/firmware_repos
Support for alternate download URL for custom firmware images
2024-03-08 01:10:34 +01:00
Mark Qvist 4947463440 Updated roadmap 2024-03-06 12:14:36 +01:00
Chad Attermann 5565349255 Fixed installation of alternate firmware version
Required version info was not being downloaded when alternate (not latest)
version is selected rsulting in the error "Could not read locally cached
release information."
2024-03-05 19:02:47 -07:00
Chad Attermann 1b7b131adc Added support for alternate firmware download URL
New command line option `--fw-url` accepts an alternate URL to use for
downloading firmware images.
Note this feature is moderately opinionated when it comes to directory
structure. The intent is to be compatible with GitHub releases, so the
latest version info is expected to be found at
"{fw-url}latest/download/release.json" and firmware images at
"{fw-url}download/{version}/{firmware_file.zip}".
2024-03-05 17:14:52 -07:00
Mark Qvist ace0d997d4 Updated changelog 2024-03-02 00:40:44 +01:00
Mark Qvist 798c252284 Updated manual 2024-03-02 00:40:35 +01:00
Mark Qvist 7da22c8580 Updated documentation build 2024-03-01 00:47:12 +01:00
Mark Qvist eefbb89cde Updated version 2024-03-01 00:05:40 +01:00
Mark Qvist 18f50ff1ae Limit amount of random blobs kept in memory and persisted to disk. Add check for non-existent announce in processing table. 2024-03-01 00:03:56 +01:00
Mark Qvist 05e97ac0db Fixed saving known destination when on-disk storage file has become corrupted 2024-02-29 23:23:41 +01:00
Mark Qvist c2c3a144d2 Added payload data inactivity metric to Link API 2024-02-29 23:05:16 +01:00
markqvist ea369015ee Update issue templates 2024-02-29 17:07:53 +01:00
markqvist 9745842862 Update issue templates 2024-02-29 17:05:46 +01:00
markqvist 246289c52d Create config.yml 2024-02-29 17:04:17 +01:00
markqvist ff71cb2f98 Update issue templates 2024-02-29 16:58:57 +01:00
Mark Qvist 5ca1ef1777 Revert EEPROM check logic 2024-02-29 16:18:39 +01:00
Mark Qvist 2b764b4af8 Allow EEPROM checksum mismatch on autoinstall. Fixes #432. 2024-02-29 15:50:45 +01:00
Mark Qvist a62843cd75 Updated readme 2024-02-16 17:54:31 +01:00
Mark Qvist 633435390d Added ability to flash T3 boards with TCXO 2024-02-16 17:32:01 +01:00
Mark Qvist 1e207ef972 Updated readme 2024-02-16 17:31:42 +01:00
Mark Qvist 35e9a0b38a Updated changelog 2024-02-14 16:58:51 +01:00
Mark Qvist 3d7f3825fb Updated manual 2024-02-14 16:54:29 +01:00
Mark Qvist 04b67a545d Updated version 2024-02-13 19:01:07 +01:00
Mark Qvist 61c2fbd0da Merge branch 'master' of github.com:markqvist/Reticulum 2024-02-13 19:00:00 +01:00
Mark Qvist 1aba4ec43a Added support for SX126x-based RNodes 2024-02-13 18:59:23 +01:00
markqvist 841a3daa26 Merge pull request #439 from jacobeva/master
Update min and max values to support SX1280
2024-02-09 22:30:32 +01:00
jacob.eva d98f03f245 Update min and max values to support SX1280 2024-02-09 21:17:58 +00:00
Mark Qvist 878e67f69d Fixed invalid RSSI offset reference. Fixes #433. 2024-01-18 23:01:54 +01:00
Mark Qvist e582a6d6d1 Updated changelog 2024-01-17 22:59:02 +01:00
Mark Qvist a948afb816 Updated manual 2024-01-17 22:56:24 +01:00
Mark Qvist 86a294388f Merge branch 'master' of github.com:markqvist/Reticulum 2024-01-17 22:52:48 +01:00
Mark Qvist 429a0b1bd3 Updated changelog 2024-01-17 22:52:01 +01:00
Mark Qvist ee8bb42633 Updated manual 2024-01-17 22:51:16 +01:00
Mark Qvist c659388a2c Updated manual 2024-01-17 22:36:17 +01:00
markqvist eaa8199988 Merge pull request #428 from jacobeva/master
Add NRF52 support
2024-01-17 01:33:07 +01:00
jacob.eva 4f890e7e8a Added NRF52 support 2024-01-16 21:30:31 +00:00
Mark Qvist a37e039424 Check input_file attribut 2024-01-14 18:57:23 +01:00
Mark Qvist 8e1e2a9c54 Added debug function 2024-01-14 18:56:20 +01:00
Mark Qvist e4f94c9d0b Updated docs 2024-01-14 18:55:44 +01:00
Mark Qvist b007530123 Adjusted resource timeout calculation 2024-01-14 01:06:43 +01:00
Mark Qvist 4066bba303 Merge branch 'master' of github.com:markqvist/Reticulum 2024-01-14 00:48:14 +01:00
Mark Qvist 8951517d01 Updated version 2024-01-14 00:47:45 +01:00
Mark Qvist ae1d962b9b Fixed large resource transfers failing under some conditions 2024-01-14 00:46:55 +01:00
Mark Qvist a2caa47334 Improved link tests 2024-01-14 00:12:30 +01:00
Mark Qvist 9f43da9105 Fixed rnprobe formatting issue 2024-01-13 16:37:48 +01:00
Mark Qvist 038c696db9 Fixed missing check on malformed advertisement packets 2024-01-13 16:36:11 +01:00
Mark Qvist 8fa6ec144c Updated readme 2024-01-03 12:05:30 +01:00
Mark Qvist a8ccff7c55 Updated contribution guidelines 2024-01-03 12:00:10 +01:00
markqvist a5783da407 Merge pull request #416 from jooray/patch-2
Fix typo
2023-12-31 12:24:48 +01:00
Juraj Bednar bec3cee425 Fix typo 2023-12-30 23:47:51 +01:00
Mark Qvist b15bd19de5 Added funding info 2023-12-30 22:00:46 +01:00
Mark Qvist 38390fd021 Updated license 2023-12-30 21:57:40 +01:00
Mark Qvist 40e0eee64f Updated license 2023-12-30 21:55:20 +01:00
Mark Qvist af4cbb1baf Added funding info 2023-12-30 21:53:50 +01:00
Mark Qvist d3f4192fe3 Added funding info 2023-12-30 21:52:41 +01:00
Mark Qvist 47ef62ac11 Updated contribution guidelines 2023-12-30 21:43:35 +01:00
Mark Qvist d15ddc7a49 Updated contribution guidelines 2023-12-30 17:34:51 +01:00
Mark Qvist d67c8eb1cd Fixed potential division by zero 2023-12-25 11:39:24 +01:00
Mark Qvist f4de5d5199 Updated changelog 2023-12-07 15:52:20 +01:00
Mark Qvist 34e42988ea Updated docs 2023-12-07 15:51:22 +01:00
Mark Qvist 81d5d41149 Updated changelog 2023-12-07 15:51:15 +01:00
Mark Qvist 6b3f3a37f0 Updated version 2023-12-06 00:07:06 +01:00
Mark Qvist 60a604f635 Carrier change flag on listener replace 2023-12-06 00:06:45 +01:00
Mark Qvist 55a2daf379 Updated docs 2023-12-02 02:14:49 +01:00
Mark Qvist 2dbde13321 Added identity import and export in hex, base32 and base64 formats 2023-12-02 02:10:22 +01:00
Mark Qvist 6620dcde6b Updated docs 2023-11-14 10:06:28 +01:00
Mark Qvist 60966d5bb1 Updated changelog 2023-11-14 10:06:19 +01:00
Mark Qvist ea22a53bf2 Updated docs 2023-11-13 23:38:46 +01:00
Mark Qvist 7b9526b4ed Updated version 2023-11-13 23:23:40 +01:00
Mark Qvist 676074187a Added timeout and wait options to rnprobe and improved output formatting 2023-11-13 23:22:58 +01:00
Mark Qvist 5dd2c31caf Generate receipts prior to raw transmit 2023-11-13 23:12:59 +01:00
Mark Qvist 2db400a1a0 Updated changelog 2023-11-13 23:11:29 +01:00
Mark Qvist b68dbaf15e Updated log levels 2023-11-08 15:23:29 +01:00
Mark Qvist 84febcdf95 Updated changelog 2023-11-06 11:28:22 +01:00
Mark Qvist c972ef90c8 Updated manual 2023-11-06 11:21:32 +01:00
Mark Qvist 19a74e3130 Updated changelog 2023-11-06 11:21:23 +01:00
Mark Qvist 5ba789f782 Updated single-packet timing 2023-11-06 11:10:38 +01:00
Mark Qvist 58b5501e17 Cleanup 2023-11-06 11:08:31 +01:00
Mark Qvist b584832b8f Fixed logging error messages when a local client connects while instance is starting up 2023-11-06 11:06:14 +01:00
Mark Qvist fc0cf17c4d Updated docs 2023-11-05 23:37:45 +01:00
Mark Qvist 001dd369ec Updated version 2023-11-05 23:37:38 +01:00
Mark Qvist 9ce2ea4a5c Updated link test 2023-11-05 23:36:19 +01:00
Mark Qvist eec8814c22 Updated version 2023-11-05 23:29:06 +01:00
Mark Qvist 7a6ed68482 Set socket options 2023-11-05 22:57:03 +01:00
Mark Qvist cd9e23f2de Updated manual 2023-11-04 23:19:08 +01:00
Mark Qvist ffa84de0bc Updated changelog 2023-11-04 23:18:59 +01:00
Mark Qvist 89d3cdba17 Updated docs 2023-11-04 18:13:26 +01:00
Mark Qvist 2ba5843f22 Updated version 2023-11-04 18:05:42 +01:00
Mark Qvist c4d0f08767 Improved resource transfers over unreliable links 2023-11-04 18:05:20 +01:00
Mark Qvist db1cdec2a2 Fixed premature request timeout 2023-11-04 17:59:27 +01:00
Mark Qvist 1eea1a6a22 Updated example 2023-11-04 17:56:20 +01:00
Mark Qvist 4a69ce5a98 Updated changelog 2023-11-02 21:44:48 +01:00
Mark Qvist 8d653cba9b Updated docs 2023-11-02 21:39:57 +01:00
Mark Qvist a6126a6bc5 Updated version 2023-11-02 21:37:16 +01:00
Mark Qvist 957c2b3bc1 Fixed invalid reference 2023-11-02 21:33:21 +01:00
Mark Qvist 494bde4e79 Updated docs 2023-11-02 18:53:22 +01:00
Mark Qvist 5e39136dff Fixed missing path state resetting on stale path rediscovery 2023-11-02 16:15:42 +01:00
Mark Qvist 4b26a86a73 Added probe count option to rnprobe 2023-11-02 16:14:38 +01:00
Mark Qvist 43a6e280c0 Fixed bluetooth read timeouts on Android in environments with hight 2.4G noise 2023-11-02 16:08:49 +01:00
Mark Qvist 237a45b2ca Don't send rediscovery requests on local originator 2023-11-02 13:33:12 +01:00
Mark Qvist b161650ced Adjusted link timings 2023-11-02 13:04:09 +01:00
Mark Qvist 24975eac31 Updated version 2023-11-02 13:03:53 +01:00
Mark Qvist 5d1ff36565 Updated docs 2023-11-02 13:03:39 +01:00
Mark Qvist 628777900e Fixed attribute 2023-11-02 12:44:57 +01:00
Mark Qvist 12e87425dc Adjusted timings 2023-11-02 12:24:42 +01:00
Mark Qvist 873f049e20 Fixed redundant rediscovery path request 2023-11-02 04:35:57 +01:00
Mark Qvist 2ea963ed03 Fixed missing timeout calculation 2023-11-02 04:35:10 +01:00
Mark Qvist 1d1276d6dd Updated changelog 2023-10-31 12:24:59 +01:00
Mark Qvist 83741724b0 Updated documentation 2023-10-31 12:24:18 +01:00
Mark Qvist a4143cfe6d Improved link error handling. Fixes #387. 2023-10-31 11:44:12 +01:00
Mark Qvist 3d645ae2f4 Updated documentation 2023-10-31 11:09:54 +01:00
Mark Qvist 5ba125c801 Updated documentation 2023-10-31 10:53:43 +01:00
Mark Qvist badb392898 Updated manual 2023-10-28 00:40:07 +02:00
Mark Qvist c0e1ce8d86 Updated documentation and manual 2023-10-28 00:28:41 +02:00
markqvist 0bc248c5e4 Merge pull request #385 from jschulthess/master
Add user systemd service to manual
2023-10-28 00:23:10 +02:00
Mark Qvist 798dfb1727 Added ability to query physical layer stats on links 2023-10-28 00:05:35 +02:00
Mark Qvist a451b987aa Updated documentation 2023-10-28 00:03:53 +02:00
Mark Qvist f01074e5b8 Implemented link establishment on ultra low bandwidth links 2023-10-27 18:16:52 +02:00
Mark Qvist 0e12442a28 Local interface bitrate simulation 2023-10-27 18:12:53 +02:00
Jürg Schulthess a4e8489a34 fix code text syntax 2023-10-25 14:09:24 +02:00
Jürg Schulthess 276b6fbd22 fix indentation 2023-10-25 14:07:34 +02:00
Jürg Schulthess 52ab08c289 add user systemd service 2023-10-25 13:31:37 +02:00
Mark Qvist 38236366cf Improved pretty print output 2023-10-24 13:24:40 +02:00
Mark Qvist af3cc3c5dd Updated version 2023-10-24 01:45:07 +02:00
Mark Qvist 35ed1f950c Updated version 2023-10-24 01:43:50 +02:00
Mark Qvist c050ef945e Updated pretty-print functions 2023-10-24 01:41:49 +02:00
Mark Qvist bed71fa3f8 Added physical layer link stats to link and packet classes 2023-10-24 01:41:12 +02:00
Mark Qvist cf125daf5c Added link quality calculation to RNode interface 2023-10-24 01:40:17 +02:00
Mark Qvist 9f425c2e8d Updated exceptions 2023-10-24 01:39:25 +02:00
Mark Qvist 0dc78241ac Updated version 2023-10-19 01:39:47 +02:00
Mark Qvist 01e963e891 Updated manual 2023-10-19 01:39:39 +02:00
Mark Qvist b3731524ac Improved path re-discovery in changing topographies 2023-10-19 00:38:41 +02:00
Mark Qvist 67c7395ea7 Improved shared interface reconnection on service restart 2023-10-18 23:18:59 +02:00
Mark Qvist fddf36a920 Updated manual 2023-10-16 19:33:13 +02:00
Mark Qvist 4f561a8c0c Added exception handling to interface detach 2023-10-16 18:54:36 +02:00
Mark Qvist 778d6105c1 Updated readme 2023-10-10 00:32:15 +02:00
Mark Qvist 60c94dc9b6 Updated readme 2023-10-10 00:29:40 +02:00
Mark Qvist f71395e449 Updated readme 2023-10-10 00:26:28 +02:00
Mark Qvist 1abacca9bf Fixed missing command definition 2023-10-08 18:02:38 +02:00
Mark Qvist 40281d5403 Updated changelog 2023-10-07 16:42:10 +02:00
112 changed files with 22023 additions and 1894 deletions
+11
View File
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: ✨ Feature Request or Idea
url: https://github.com/markqvist/Reticulum/discussions/new?category=ideas
about: Propose and discuss features and ideas
- name: 💬 Questions, Help & Discussion
about: Ask anything, or get help
url: https://github.com/markqvist/Reticulum/discussions/new/choose
- name: 📖 Read the Reticulum Manual
url: https://markqvist.github.io/Reticulum/manual/
about: The complete documentation for Reticulum
+35
View File
@@ -0,0 +1,35 @@
---
name: "\U0001F41B Bug Report"
about: Report a reproducible bug
title: ''
labels: ''
assignees: ''
---
**Read the Contribution Guidelines**
Before creating a bug report on this issue tracker, you **must** read the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md). Issues that do not follow the contribution guidelines **will be deleted without comment**.
- The issue tracker is used by developers of this project. **Do not use it to ask general questions, or for support requests**.
- Ideas and feature requests can be made on the [Discussions](https://github.com/markqvist/Reticulum/discussions). **Only** feature requests accepted by maintainers and developers are tracked and included on the issue tracker. **Do not post feature requests here**.
- After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), delete this section from your bug report.
**Describe the Bug**
A clear and concise description of what the bug is.
**To Reproduce**
Describe in detail how to reproduce the bug.
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Logs & Screenshots**
Please include any relevant log output. If applicable, also add screenshots to help explain your problem.
**System Information**
- OS and version
- Python version
- Program version
**Additional context**
Add any other context about the problem here.
+28
View File
@@ -0,0 +1,28 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Test suite
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Test
run: |
make test
+407 -2
View File
@@ -1,6 +1,411 @@
### 2024-10-11: RNS β 0.8.4
This release fixes a number of bugs and improves reliability of automatic reconnection when BLE-connected RNodes unexpectedly disappear or lose connection.
**Changes**
- Improved RNode BLE reconnection realiability
- Added RNode battery state to `rnstatus` output
- Fixed resource transfer hanging for a long time over slow links if proof packet is lost
- Fixed missing import on Android
**Release Hashes**
```
d3f7a9fddc6c1e59b1e4895756fe602408ac6ef09de377ee65ec62d09fff97a3 dist/rns-0.8.4-py3-none-any.whl
eb3843bcab1428be0adb097988991229a4c03156ab40cc9c6e2d9c590d8b850b dist/rnspure-0.8.4-py3-none-any.whl
```
### 2024-10-10: RNS β 0.8.3
This release fixes a bug in resource transfer progress calculation, improves RNode error handling, and brings minor improvements to the `rncp` utility.
**Changes**
- Fixed a bug in resource transfer progress calculations
- Added physical layer transfer rate output option to `rncp`
- Added save directory option to `rncp`
- Improved path handling for the fetch-jail option of of `rncp`
- Added error detection for modem communication timeouts on connected RNode devices
**Release Hashes**
```
54ddab32769081045db5fe45b27492cc012bf2fad64bc65ed37011f3651469fb rns-0.8.3-py3-none-any.whl
a04915111d65b05a5f2ef2687ed208813034196c0c5e711cb01e6db72faa23ef rnspure-0.8.3-py3-none-any.whl
```
### 2024-10-06: RNS β 0.8.2
This release adds several new boards to `rnodeconf`, fixes a range of bugs and improves transport reliability.
Thanks to @jacobeva, @prusnak and @deavmi who contributed to this release!
**Changes**
- Added support for T-Beam Supreme devices to `rnodeconf`
- Added support for T3S3 devices to `rnodeconf`
- Added support for T-Deck devices to `rnodeconf`
- Added support for new hardware error codes from connected RNodes
- Added the ability to control the display on nRF52-based RNodes
- Improved resource transfers over very slow links, by adding more suitable `MAX_WINDOW` cap if link speed is continously below threshold.
- Improved `rnodeconf` flashing so manual resets for some devices are no longer required
- Added edge case handling for receiving a link proof after the link had timed out and been closed, but before it having been purged from active links table
- Updated supported hardware section of the manual with new boards
- Tuned path request timing for roaming instances
- Fixed a bug that caused RNS to fail to initialise in Termux on Android
- Fixed a bug in RNodeInterface firmware version comparison
- Fixed a bug in the serial framing of RNodeMultiInterface
- Fixed a bug in sub-interface spawning of RNodeMultiInterface
**Release Hashes**
```
db720a727a09c0c9d76288dec5a995a30146e65d6a4c5c034f47fb60a78f4962 rns-0.8.2-py3-none-any.whl
ee412535edba48817551658247fb0c843d17e1c97cad9d2a819a7fc627c5ba28 rnspure-0.8.2-py3-none-any.whl
```
### 2024-10-02: RNS β 0.8.1
This release adds BLE support to RNodeInterface, and support for configuring additional options to `rnodeconf`.
**Changes**
- Added Bluetooth Low Energy support to RNodeInterface
- Added RNode battery information to `rnstatus` output
- Added display blanking configuration to `rnodeconf`
- Added NeoPixel intensity configuration to `rnodeconf`
**Release Hashes**
```
f4b6b99b67d6b33b8a4562e5d5d5ac54c76814fff26e6c7a79950b82bd80123f rns-0.8.1-py3-none-any.whl
c2e540b4bf0f272bb51ae3e33a02f9c07f2619746d069d7ed83d88017bf7ea30 rnspure-0.8.1-py3-none-any.whl
```
### 2024-09-25: RNS β 0.8.0
This maintenance release improves the interface statistics API, and updates documentation.
**Changes**
- Added additional information to interface statistics
- Updated documentation
**Release Hashes**
```
fa5ff6d98230693be6805bb9a94585a6f54ec0af9cba15b771d4e676f140dc43 rns-0.8.0-py3-none-any.whl
ba20f688b69ae861c8aced251e10242a358fea15da6c22df10d4fc8846c9bf48 rnspure-0.8.0-py3-none-any.whl
```
### 2024-09-24: RNS β 0.7.9
This maintenance release improves transport reliability in certain (rare) cases.
**Changes**
- Added handling of a transport edge-case
**Release Hashes**
```
4c20c46df021d366386d497145024396f904666b0de22a92f9e5c937886ea39d rns-0.7.9-py3-none-any.whl
97d26282df929eca732a15523bc9d7f66387a93ffd911e8063c94c3f8f6ad73c rnspure-0.7.9-py3-none-any.whl
```
### 2024-09-18: RNS β 0.7.8
This maintenance release adds support for the openCom XL to `rnodeconf`, fixes a number of bugs, and also includes a few fine-tunings of timing parameters.
Thanks to @liamcottle and @jacobeva for contributing to this release!
**Changes**
- Added interface prioritisation according to reported bitrate
- Added support for openCom XL to `rnodeconf`
- Added performance profiler to built-in debugging tools
- Tuned link traffic timeouts
- Fixed a module import error in AX25KissInterface
- Fixed a missing exception on erroneous destination initialisation
**Release Hashes**
```
33fb9443e3b327d1a9125baa52d8ec3208a089dda62f749b819e0a94c06730f9 rns-0.7.8-py3-none-any.whl
cdced2adef4ead146239d0510fe2b9d62f69136bcd54b22d1080686fb56f9927 rnspure-0.7.8-py3-none-any.whl
```
### 2024-09-09: RNS β 0.7.7
This release adds support for automatic encryption key ratcheting for all packets, not just those sent over Reticulum links. In practical terms, this adds forward secrecy to packets sent with the raw `Packet` API.
In this release, the ratchets feature must be enabled on a per-destination basis by calling the `enable_ratchets` method on the relevant destination. In a future release, ratchets may become the default option, but for backwards-compatibility, it is currently optional. For more information, read the API documentation.
**Please note!** Versions of RNS prior to `0.7.7` will not be able to pass announces for destinations with ratchets enabled! If you use applications that can use ratchets (for example, LXMF version `0.5.0` and up), it is important that you update all transport instances on your network to `0.7.7`.
Thanks to @deavmi, @faragher, @jacobeva, @jeremy and @jeremybox for contributing to this release!
**Changes**
- Added key ratchet rotation and signalling
- Added ratchet API to documentation
- Added initial support for flashing T-Echo devices to `rnodeconf`
- Added remote management config options to example config
- Added automtic integration tests to source repository
- Fixed a regression that caused RNS not to work on Python versions lower than 3.10
- Fixed missing `establishment_rate` property init on Link objects
**Release Hashes**
```
0a3ab6dc82567a19adabe737358daee3002b60beda8ac0bf228f2a0c134ff6d8 rns-0.7.7-py3-none-any.whl
89b33fe9ab923139d3f5d43726d92817642be05a8c9d328c3becfc3c409e4b4b rnspure-0.7.7-py3-none-any.whl
```
### 2024-05-18: RNS β 0.7.6
This release adds support for RNodes with multiple radio transceivers, courtesy of @jacobeva. It also brings a number of functionality and performance improvements, and fixes several bugs.
Thanks to @jacobeva, @faragher, @nathmo, @jschulthess and @liamcottle for contributing to this release!
**Changes**
- Added support for RNode Multi interfaces
- Added initial support for remote management of Reticulum instances
- Improved resource transfer performance for large resources
- Improved path rediscovery in topologies with roaming transport nodes
- Fixed incorrect TX power limit on Android RNode interfaces
- Added ability to fetch remote files to `rncp`
- Added fetch request jail option to `rncp`
- Improved `rncp` status display output
- Added link table statistics to `rnstatus`
- Fixed `rnstatus` JSON output bug when IFAC was enabled on an interface
- Added remote instance interface status to `rnstatus`
- Added ability to query path- and rate-tables on remote instances with `rnpath`
- Added JSON output option to `rnpath` utility
- Added max hops filter to `rnpath` path-table out
- Added link age getter to API
- Added request concluded status to API
- Fixed invalid resource progress reported in some cases
- Fixed `rnodeconf` failure to set firmware hash for NRF52 boards on macOS
- Fixed broken `--rom` command line option in `rnodeconf`
- Fixed various typos in documentation
- Updated documentation with new API functions and features
**Release Hashes**
```
683ac87c62fe8a18d88c26bf639f4eeca550cefb11ee8e38d6e724e268cf14fc rns-0.7.6-py3-none-any.whl
f884806624e57b799f588de9289a31d2e0460d35bc4cc5071635de5642d50ad2 rnspure-0.7.6-py3-none-any.whl
```
### 2024-05-18: RNS β 0.7.5
This release adds support for AutoInterface on Windows platforms, fixes a number of bugs and adds several new supported boards to `rnodeconf`. Thanks to @faragher, @jacobeva and @liamcottle who contributed to this release!
**Changes**
- Added support for AutoInterface on Windows
- Added support for recursive path resolution for clients on roaming-mode interfaces
- Added RAK4631 support to `rnodeconf`
- Added LilyGO T3S3 support to `rnodeconf`
- Added ability to get target and calculated hashes via `rnodeconf`
- Fixed DTR timing making flashing fail on Windows in `rnodeconf`
- Fixed various output and menu bugs in `rnodeconf`
**Release Hashes**
```
99ec876966afdea45fcf164242c8e76c284f9e3edf09fb907638fba76e1324b1 rns-0.7.5-py3-none-any.whl
11156f6301707e4d17ff2ca6d58059bc8ba6fe1bbc4dc3de165dd96dc41ee75f rnspure-0.7.5-py3-none-any.whl
```
### 2024-05-05: RNS β 0.7.4
This maintenance release fixes a number of bugs, improves path requests and responses, and adds several useful features and capabilities. Thanks to @cobraPA, @jschulthess, @thiaguetz and @nothingbutlucas who contributed to this release!
**Changes**
- Added support for flashing and autoinstalling Heltec V3 boards to `rnodeconf`
- Added custom EEPROM bootstrapping capabilities to `rnodeconf`
- Added ability to load identities from file to Echo and Link examples
- Added ability to specify multicast address type in AutoInterface configuration
- Added link getter to resource advertisement class
- Improved path response logic and timing
- Improved path request timing
- Fixed a bug in Link Request proof delivery on unknown hop count paths
- Fixed broken link packet routing in topologies where transport packets leak to non-intended instances in the link chain
- Fixed typos in documentation
**Release Hashes**
```
f5c35f1b8720778eb508b687d66334d01b4ab266b2d8c2bc186702220dcaae29 rns-0.7.4-py3-none-any.whl
9eaa7170f97dad49551136965d3fcc971b56b1c2eda48c24b9ffd58d71daa016 rnspure-0.7.4-py3-none-any.whl
```
### 2024-03-09: RNS β 0.7.3
This release adds the ability to specify custom firmware URLs for flashing boards with `rnodeconf`. Thanks to @attermann who contributed to this release!
**Changes**
- Added ability to specify custom firmware URLs for flashing boards with `rnodeconf`
**Release Hashes**
```
bb24445ae9a3a63d348e4d7fe80b750608f257851b97b38fadab929b7a774bc9 rns-0.7.3-py3-none-any.whl
1b148d013103c35ba9a8e105082ef50686c130676d0a560ed709cb546129287e rnspure-0.7.3-py3-none-any.whl
```
### 2024-03-02: RNS β 0.7.2
This maintenance release improves memory consumption, fixes a few bugs, and adds ability to flash new boards with `rnodeconf`.
**Changes**
- Added ability to flash new boards with `rnodeconf`, including T3 boards with TCXOs
- Improved memory consumption on Transport Instances with many interfaces
- Fixed a bug that could cause the on-disk known destinations store to become corrupted
**Release Hashes**
```
3ce3ba80d5ae8d19c6b55bd51f44bd4beccbcea31554cb1f0d65428e4587b3d6 rns-0.7.2-py3-none-any.whl
83f914aaba2a8929a8cee95830a847e190197232a0cca4e7b906b15c6bbf8296 rnspure-0.7.2-py3-none-any.whl
```
### 2024-02-14: RNS β 0.7.1
This release adds support for RNodes based on SX1262, SX1268 and SX1280 modems, and fixes a number of bugs. Thanks to @jacobeva, who contributed to this release!
**Changes**
- Added support for SX1262, SX1268 and SX1280 based RNodes
- Updated `rnodeconf` to allow flashing T-Beam devices with SX126x chips
- Fixed an invalid RSSI offset reference
**Release Hashes**
```
8ecfbb42b6a699fd4ac5374ab5640e4bb164e80bb9ab4401ea82da132e497877 rns-0.7.1-py3-none-any.whl
e0ab487305ba1aee2d16044640e7eb72d332bbf51aeb0b8bf984d037a64cb493 rnspure-0.7.1-py3-none-any.whl
```
### 2024-01-17: RNS β 0.7.0
This maintenance release fixes a number of bugs. Thanks to @jooray and @jacobeva, who contributed to this release!
**Changes**
- Fixed large resource transfers failing under some conditions
- Fixed a potential division by zero
- Fixed a missing check on malformed advertisement packets
- Fixed a formatting issue in `rnprobe`
- Improved resource timeout calculations
**Release Hashes**
```
0dc2abe5373b9afadfba7ec05bf7ddeff659c004aa339a94001ebed5b46f5b47 rns-0.7.0-py3-none-any.whl
97f6e65a20b53bbdccd54b4d2bdaa36dc1712e144a55f40800c63fe7113819a5 rnspure-0.7.0-py3-none-any.whl
```
### 2023-12-07: RNS β 0.6.9
This release adds a few convenience functions to the `rnid` utility, and improves roaming support on Android.
**Changes**
- Added identity import and export in hex, base32 and base64 formats to the `rnid` utility.
- Added better carrier change detection for AutoInterface on Android.
**Release Hashes**
```
258daf22cb6e72c6cd04fe94447daedf51dfd968eb2f3370eab9c71ad0898dd0 rns-0.6.9-py3-none-any.whl
3644b64af5b4efd3969172bf0cf95ae1afba6c8ea99ce47d8e49e31a832bbaf8 rnspure-0.6.9-py3-none-any.whl
```
### 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 file, making it possible to use all Reticulum functionality in the terminal on Android.
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
@@ -943,4 +1348,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.
+27 -7
View File
@@ -2,22 +2,42 @@
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.
Apart from writing code, there are many ways in which you can contribute. Before interacting with this community, read these short and simple guidelines.
## Expected Conduct
First and foremost, there is one simple requirement for taking part in this community: While we primarily interact virtually, your actions matter and have real consequences. Therefore: **Act like a responsible, civilized person** - also in the face of disputes and heated disagreements. Speak your mind here, discussions are welcome. Just do so in the spirit of being face-to-face with everyone else. Thank you.
## Asking Questions
If you want to ask a question, do not open an issue.
If you want to ask a question, **do not open an issue**. The issue tracker is used by people *working on Reticulum* to track bugs, issues and improvements.
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`
Instead, ask away on the [discussions](https://github.com/markqvist/Reticulum/discussions) or on the [Reticulum Matrix channel](https://matrix.to/#/#reticulum:matrix.org) at `#reticulum:matrix.org`
## Providing Feedback
## Providing Feedback & Ideas
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`
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://matrix.to/#/#reticulum:matrix.org) at `#reticulum:matrix.org`.
Please do not post feature requests or general ideas on the issue tracker, or in direct messages to the primary developers. You are much more likely to get a response and start a constructive discussion by posting your ideas in the public channels created for these purposes.
## 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).
If you have found a bug or issue in this project, please report it using the [issue tracker](https://github.com/markqvist/Reticulum/issues). If at all possible, be sure to include details on how to reproduce the bug.
Anything submitted to the issue tracker that does not follow these guidelines will be closed and removed without comments or explanation.
## 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.
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 **before** submitting a pull request. Before deciding to contribute, it is also a good idea to ensure your efforts are in alignment with the [Roadmap](./Roadmap.md) and current development focus.
Pull requests have a high chance of being accepted if they are:
- In alignment with the [Roadmap](./Roadmap.md) or solve an open issue or feature request
- Sufficiently tested to work with all API functions, and pass the standard test suite
- Functionally and conceptually complete and well-designed
- Not simply formatting or code style changes
- Well-documented
Even new ideas and proposals that have not been approved by a maintainer, or fall outside the established roadmap, are *occasionally* accepted - if they possess the remaining of the above qualities. If not, they will be closed and removed without comments or explanation.
By contributing code to this project, you agree that copyright for the code is transferred to the Reticulum maintainers and that the code is irrevocably placed under the [MIT license](./LICENSE).
+1 -2
View File
@@ -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
+343
View File
@@ -0,0 +1,343 @@
##########################################################
# This RNS example demonstrates a simple client/server #
# echo utility that uses ratchets to rotate encryption #
# keys everytime an announce is sent. #
##########################################################
import argparse
import RNS
# Let's define an app name. We'll use this for all
# destinations we create. Since this echo example
# is part of a range of example utilities, we'll put
# them all within the app namespace "example_utilities"
APP_NAME = "example_utilities"
##########################################################
#### Server Part #########################################
##########################################################
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
global reticulum
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# TODO: Remove
RNS.loglevel = RNS.LOG_DEBUG
# Randomly create a new identity for our echo server
server_identity = RNS.Identity()
# We create a destination that clients can query. We want
# to be able to verify echo replies to our clients, so we
# create a "single" destination that can receive encrypted
# messages. This way the client can send a request and be
# certain that no-one else than this destination was able
# to read it.
echo_destination = RNS.Destination(
server_identity,
RNS.Destination.IN,
RNS.Destination.SINGLE,
APP_NAME,
"ratchet",
"echo",
"request"
)
# Enable ratchets on the destination by providing a file
# path to store ratchets. In this example, we will just
# use a temporary file, but in real-world applications,
# it's extremely important to keep this file secure, since
# it contains encryption keys for the destination.
destination_hexhash = RNS.hexrep(echo_destination.hash, delimit=False)
echo_destination.enable_ratchets(f"/tmp/{destination_hexhash}.ratchets")
# We configure the destination to automatically prove all
# packets addressed to it. By doing this, RNS will automatically
# generate a proof for each incoming packet and transmit it
# back to the sender of that packet.
echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
# Tell the destination which function in our program to
# run when a packet is received. We do this so we can
# print a log message when the server receives a request
echo_destination.set_packet_callback(server_callback)
# Everything's ready!
# Let's Wait for client requests or user input
announceLoop(echo_destination)
def announceLoop(destination):
# Let the user know that everything is ready
RNS.log(
"Ratcheted echo server "+
RNS.prettyhexrep(destination.hash)+
" running, hit enter to manually send an announce (Ctrl-C to quit)"
)
# We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server
# destination on the network, which will let clients
# know how to create messages directed towards it.
while True:
entered = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
def server_callback(message, packet):
global reticulum
# Tell the user that we received an echo request, and
# that we are going to send a reply to the requester.
# Sending the proof is handled automatically, since we
# set up the destination to prove all incoming packets.
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(packet.packet_hash)
reception_snr = reticulum.get_packet_snr(packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dBm]"
else:
if packet.rssi != None:
reception_stats += " [RSSI "+str(packet.rssi)+" dBm]"
if packet.snr != None:
reception_stats += " [SNR "+str(packet.snr)+" dB]"
RNS.log("Received packet from echo client, proof sent"+reception_stats)
##########################################################
#### Client Part #########################################
##########################################################
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath, timeout=None):
global reticulum
# We need a binary representation of the destination
# hash that was entered on the command line
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except Exception as e:
RNS.log("Invalid destination entered. Check your input!")
RNS.log(str(e)+"\n")
exit()
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# We override the loglevel to provide feedback when
# an announce is received
if RNS.loglevel < RNS.LOG_INFO:
RNS.loglevel = RNS.LOG_INFO
# Tell the user that the client is ready!
RNS.log(
"Echo client ready, hit enter to send echo request to "+
destination_hexhash+
" (Ctrl-C to quit)"
)
# We enter a loop that runs until the user exits.
# If the user hits enter, we will try to send an
# echo request to the destination specified on the
# command line.
while True:
input()
# Let's first check if RNS knows a path to the destination.
# If it does, we'll load the server identity and create a packet
if RNS.Transport.has_path(destination_hash):
# To address the server, we need to know it's public
# key, so we check if Reticulum knows this destination.
# This is done by calling the "recall" method of the
# Identity module. If the destination is known, it will
# return an Identity instance that can be used in
# outgoing destinations.
server_identity = RNS.Identity.recall(destination_hash)
# We got the correct identity instance from the
# recall method, so let's create an outgoing
# destination. We use the naming convention:
# example_utilities.ratchet.echo.request
# This matches the naming we specified in the
# server part of the code.
request_destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"ratchet",
"echo",
"request"
)
# The destination is ready, so let's create a packet.
# We set the destination to the request_destination
# that was just created, and the only data we add
# is a random hash.
echo_request = RNS.Packet(request_destination, RNS.Identity.get_random_hash())
# Send the packet! If the packet is successfully
# sent, it will return a PacketReceipt instance.
packet_receipt = echo_request.send()
# If the user specified a timeout, we set this
# timeout on the packet receipt, and configure
# a callback function, that will get called if
# the packet times out.
if timeout != None:
packet_receipt.set_timeout(timeout)
packet_receipt.set_timeout_callback(packet_timed_out)
# We can then set a delivery callback on the receipt.
# This will get automatically called when a proof for
# this specific packet is received from the destination.
packet_receipt.set_delivery_callback(packet_delivered)
# Tell the user that the echo request was sent
RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash))
else:
# If we do not know this destination, tell the
# user to wait for an announce to arrive.
RNS.log("Destination is not yet known. Requesting path...")
RNS.log("Hit enter to manually retry once an announce is received.")
RNS.Transport.request_path(destination_hash)
# This function is called when our reply destination
# receives a proof packet.
def packet_delivered(receipt):
global reticulum
if receipt.status == RNS.PacketReceipt.DELIVERED:
rtt = receipt.get_rtt()
if (rtt >= 1):
rtt = round(rtt, 3)
rttstring = str(rtt)+" seconds"
else:
rtt = round(rtt*1000, 3)
rttstring = str(rtt)+" milliseconds"
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dB]"
else:
if receipt.proof_packet != None:
if receipt.proof_packet.rssi != None:
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
if receipt.proof_packet.snr != None:
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
RNS.log(
"Valid reply received from "+
RNS.prettyhexrep(receipt.destination.hash)+
", round-trip time is "+rttstring+
reception_stats
)
# This function is called if a packet times out.
def packet_timed_out(receipt):
if receipt.status == RNS.PacketReceipt.FAILED:
RNS.log("Packet "+RNS.prettyhexrep(receipt.hash)+" timed out")
##########################################################
#### Program Startup #####################################
##########################################################
# This part of the program gets run at startup,
# and parses input from the user, and then starts
# the desired program mode.
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="Simple ratcheted echo server and client utility")
parser.add_argument(
"-s",
"--server",
action="store_true",
help="wait for incoming packets from clients"
)
parser.add_argument(
"-t",
"--timeout",
action="store",
metavar="s",
default=None,
help="set a reply timeout in seconds",
type=float
)
parser.add_argument("--config",
action="store",
default=None,
help="path to alternative Reticulum config directory",
type=str
)
parser.add_argument(
"destination",
nargs="?",
default=None,
help="hexadecimal hash of the server destination",
type=str
)
args = parser.parse_args()
if args.server:
configarg=None
if args.config:
configarg = args.config
server(configarg)
else:
if args.config:
configarg = args.config
else:
configarg = None
if args.timeout:
timeoutarg = float(args.timeout)
else:
timeoutarg = None
if (args.destination == None):
print("")
parser.print_help()
print("")
else:
client(args.destination, configarg, timeout=timeoutarg)
except KeyboardInterrupt:
print("")
exit()
+2
View File
@@ -0,0 +1,2 @@
ko_fi: markqvist
custom: "https://unsigned.io/donate"
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License, unless otherwise noted
Copyright (c) 2016-2022 Mark Qvist / unsigned.io
Copyright (c) 2016-2024 Mark Qvist / unsigned.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -2,7 +2,7 @@ all: release
test:
@echo Running tests...
python -m tests.all
python3 -m tests.all
clean:
@echo Cleaning...
+42 -29
View File
@@ -1,4 +1,4 @@
Reticulum Network Stack β <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Installs"/>
Reticulum Network Stack β <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Installs" style="padding-left:10px"/><a href="https://github.com/markqvist/reticulum/actions/workflows/python-app.yml"><img align="right" src="https://github.com/markqvist/reticulum/actions/workflows/python-app.yml/badge.svg"/></a>
==========
<p align="center"><img width="200" src="https://raw.githubusercontent.com/markqvist/Reticulum/master/docs/source/graphics/rns_logo_512.png"></p>
@@ -37,25 +37,27 @@ The full documentation for Reticulum is available at [markqvist.github.io/Reticu
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/)
For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ section of the wiki](https://github.com/markqvist/Reticulum/wiki/Frequently-Asked-Questions).
## Notable Features
- Coordination-less globally unique addressing and identification
- Fully self-configuring multi-hop routing
- Fully self-configuring multi-hop routing over heterogeneous carriers
- Initiator anonymity, communicate without revealing your identity
- Reticulum does not include source addresses on any packets
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
- Forward Secrecy with ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for on-the-wire / over-the-air encryption
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
- The foundational Reticulum Identity Keys are 512-bit Elliptic Curve keysets
- Forward Secrecy is available for all communication types, both for single packets and over links
- Reticulum uses the following format for encrypted tokens:
- Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
- AES-128 in CBC mode with PKCS7 padding
- HMAC using SHA256 for authentication
- IVs are generated through os.urandom()
- Unforgeable packet delivery confirmations
- A variety of supported interface types
- A large variety of supported interface types
- An intuitive and easy-to-use API
- Reliable and efficient transfer of arbitrary amounts of data
- Reticulum can handle a few bytes of data or files of many gigabytes
- Sequencing, transfer coordination and checksumming are automatic
- Sequencing, compression, transfer coordination and checksumming are automatic
- The API is very easy to use, and provides transfer progress
- Lightweight, flexible and expandable Request/Response mechanism
- Efficient link establishment
@@ -74,13 +76,14 @@ 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
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
- The Android, Linux, macOS and Windows app [Sideband](https://github.com/markqvist/Sideband) has a graphical interface and focuses on ease of use.
- [MeshChat](https://github.com/liamcottle/reticulum-meshchat) is a user-friendly LXMF client, that also supports voice calls.
## 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.
@@ -191,8 +194,8 @@ 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
to 20 megabits per second, with physical mediums faster than that not being
Currently, the usable performance envelope is approximately 150 bits per second
to 40 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.
@@ -233,10 +236,17 @@ Primitives](#cryptographic-primitives) section of this document.
## Public Testnet
If you just want to get started experimenting without building any physical
networks, you are welcome to join the Unsigned.io RNS Testnet. The testnet is
just that, an informal network for testing and experimenting. It will be up
most of the time, and anyone can join, but it also means that there's no
guarantees for service availability.
networks, you are welcome to join the RNS Development Testnet.
The testnet is just that, an informal network for testing and experimenting.
It will be up most of the time, and anyone can join, but it also means that
there's no guarantees for service availability.
It probably goes without saying, but *don't use the testnet entry-points as
hardcoded or default interfaces in any applications you ship to users*. When
shipping applications, the best practice is to provide your own default
connectivity solutions, if needed and applicable, or in most cases, simply
leave it up to the user which networks to connect to, and how.
The testnet runs the very latest version of Reticulum (often even a short while
before it is publicly released). Sometimes experimental versions of Reticulum
@@ -257,7 +267,7 @@ file:
[[RNS Testnet BetweenTheBorders]]
type = TCPClientInterface
enabled = yes
target_host = betweentheborders.com
target_host = reticulum.betweentheborders.com
target_port = 4242
# Interface to Testnet I2P Hub
@@ -273,16 +283,16 @@ The testnet also contains a number of [Nomad Network](https://github.com/markqvi
You can help support the continued development of open, free and private communications systems by donating via one of the following channels:
- Monero:
```
```
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
```
- Ethereum
```
0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73
```
- Bitcoin
```
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH
```
- Ko-Fi: https://ko-fi.com/markqvist
@@ -290,17 +300,20 @@ Are certain features in the development roadmap are important to you or your
organisation? Make them a reality quickly by sponsoring their implementation.
## Cryptographic Primitives
Reticulum uses a simple suite of efficient, strong and modern cryptographic
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic
primitives, with widely available implementations that can be used both on
general-purpose CPUs and on microcontrollers. The necessary primitives are:
general-purpose CPUs and on microcontrollers. The utilised primitives are:
- Ed25519 for signatures
- X22519 for ECDH key exchanges
- Reticulum Identity Keys are 512-bit Curve25519 keysets
- A 256-bit Ed25519 key for signatures
- A 256-bit X22519 key for ECDH key exchanges
- HKDF for key derivation
- Modified Fernet for encrypted tokens
- AES-128 in CBC mode
- HMAC for message authentication
- No Fernet version and timestamp fields
- Encrypted tokens are based on the [Fernet spec](https://github.com/fernet/spec/)
- Ephemeral keys derived from an ECDH key exchange on Curve25519
- AES-128 in CBC mode with PKCS7 padding
- HMAC using SHA256 for message authentication
- IVs are generated through os.urandom()
- No Fernet version and timestamp metadata fields
- SHA-256
- SHA-512
+6 -6
View File
@@ -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")
+192 -20
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -20,11 +20,14 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import math
import time
import threading
import RNS
from RNS.Cryptography import Fernet
from .vendor import umsgpack as umsgpack
class Callbacks:
def __init__(self):
@@ -38,14 +41,14 @@ class Destination:
instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its
presence on the network, which will also distribute necessary keys for
presence on the network, which will distribute necessary keys for
encrypted communication with it.
:param identity: An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
:param app_name: A string specifying the app name.
:param \*aspects: Any non-zero number of string arguments.
:param \\*aspects: Any non-zero number of string arguments.
"""
# Constants
@@ -71,6 +74,16 @@ class Destination:
PR_TAG_WINDOW = 30
RATCHET_COUNT = 512
"""
The default number of generated ratchet keys a destination will retain, if it has ratchets enabled.
"""
RATCHET_INTERVAL = 30*60
"""
The minimum interval between rotating ratchet keys, in seconds.
"""
@staticmethod
def expand_name(identity, app_name, *aspects):
"""
@@ -137,6 +150,14 @@ class Destination:
self.type = type
self.direction = direction
self.proof_strategy = Destination.PROVE_NONE
self.ratchets = None
self.ratchets_path = None
self.ratchet_interval = Destination.RATCHET_INTERVAL
self.ratchet_file_lock = threading.Lock()
self.retained_ratchets = Destination.RATCHET_COUNT
self.latest_ratchet_time = None
self.latest_ratchet_id = None
self.__enforce_ratchets = False
self.mtu = 0
self.path_responses = {}
@@ -146,6 +167,9 @@ class Destination:
identity = RNS.Identity()
aspects = aspects+(identity.hexhash,)
if identity == None and direction == Destination.OUT and self.type != Destination.PLAIN:
raise ValueError("Can't create outbound SINGLE destination without an identity")
if identity != None and self.type == Destination.PLAIN:
raise TypeError("Selected destination type PLAIN cannot hold an identity")
@@ -170,6 +194,39 @@ class Destination:
"""
return "<"+self.name+"/"+self.hexhash+">"
def _clean_ratchets(self):
if self.ratchets != None:
if len (self.ratchets) > self.retained_ratchets:
self.ratchets = self.ratchets[:Destination.RATCHET_COUNT]
def _persist_ratchets(self):
try:
with self.ratchet_file_lock:
packed_ratchets = umsgpack.packb(self.ratchets)
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
ratchets_file = open(self.ratchets_path, "wb")
ratchets_file.write(umsgpack.packb(persisted_data))
ratchets_file.close()
except Exception as e:
self.ratchets = None
self.ratchets_path = None
raise OSError("Could not write ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def rotate_ratchets(self):
if self.ratchets != None:
now = time.time()
if now > self.latest_ratchet_time+self.ratchet_interval:
RNS.log("Rotating ratchets for "+str(self), RNS.LOG_DEBUG)
new_ratchet = RNS.Identity._generate_ratchet()
self.ratchets.insert(0, new_ratchet)
self.latest_ratchet_time = now
self._clean_ratchets()
self._persist_ratchets()
return True
else:
raise SystemError("Cannot rotate ratchet on "+str(self)+", ratchets are not enabled")
return False
def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
"""
@@ -185,6 +242,7 @@ class Destination:
if self.direction != Destination.IN:
raise TypeError("Only IN destination types can be announced")
ratchet = b""
now = time.time()
stale_responses = []
for entry_tag in self.path_responses:
@@ -211,6 +269,11 @@ class Destination:
destination_hash = self.hash
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
if self.ratchets != None:
self.rotate_ratchets()
ratchet = RNS.Identity._ratchet_public_bytes(self.ratchets[0])
RNS.Identity._remember_ratchet(self.hash, ratchet)
if app_data == None and self.default_app_data != None:
if isinstance(self.default_app_data, bytes):
app_data = self.default_app_data
@@ -219,13 +282,12 @@ class Destination:
if isinstance(returned_app_data, bytes):
app_data = returned_app_data
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash+ratchet
if app_data != None:
signed_data += app_data
signature = self.identity.sign(signed_data)
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+signature
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+ratchet+signature
if app_data != None:
announce_data += app_data
@@ -237,8 +299,13 @@ class Destination:
else:
announce_context = RNS.Packet.NONE
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
if ratchet:
context_flag = RNS.Packet.FLAG_SET
else:
context_flag = RNS.Packet.FLAG_UNSET
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context,
attached_interface = attached_interface, context_flag=context_flag)
if send:
announce_packet.send()
else:
@@ -298,7 +365,6 @@ class Destination:
else:
self.proof_strategy = proof_strategy
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
"""
Registers a request handler.
@@ -320,7 +386,6 @@ class Destination:
request_handler = [path, response_generator, allow, allowed_list]
self.request_handlers[path_hash] = request_handler
def deregister_request_handler(self, path):
"""
Deregisters a request handler.
@@ -335,14 +400,13 @@ class Destination:
else:
return False
def receive(self, packet):
if packet.packet_type == RNS.Packet.LINKREQUEST:
plaintext = packet.data
self.incoming_link_request(plaintext, packet)
else:
plaintext = self.decrypt(packet.data)
packet.ratchet_id = self.latest_ratchet_id
if plaintext != None:
if packet.packet_type == RNS.Packet.DATA:
if self.callbacks.packet != None:
@@ -351,13 +415,103 @@ class Destination:
except Exception as e:
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def incoming_link_request(self, data, packet):
if self.accept_link_requests:
link = RNS.Link.validate_request(self, data, packet)
if link != None:
self.links.append(link)
def _reload_ratchets(self, ratchets_path):
if os.path.isfile(ratchets_path):
with self.ratchet_file_lock:
try:
ratchets_file = open(ratchets_path, "rb")
persisted_data = umsgpack.unpackb(ratchets_file.read())
if "signature" in persisted_data and "ratchets" in persisted_data:
if self.identity.validate(persisted_data["signature"], persisted_data["ratchets"]):
self.ratchets = umsgpack.unpackb(persisted_data["ratchets"])
self.ratchets_path = ratchets_path
else:
raise KeyError("Invalid ratchet file signature")
except Exception as e:
self.ratchets = None
self.ratchets_path = None
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
self.ratchets = []
self.ratchets_path = ratchets_path
self._persist_ratchets()
def enable_ratchets(self, ratchets_path):
"""
Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate
the keys used to encrypt packets to this destination, and include the latest ratchet key in announces.
Enabling ratchets on a destination will provide forward secrecy for packets sent to that destination,
even when sent outside a ``Link``. The normal Reticulum ``Link`` establishment procedure already performs
its own ephemeral key exchange for each link establishment, which means that ratchets are not necessary
to provide forward secrecy for links.
Enabling ratchets will have a small impact on announce size, adding 32 bytes to every sent announce.
:param ratchets_path: The path to a file to store ratchet data in.
:returns: True if the operation succeeded, otherwise False.
"""
if ratchets_path != None:
self.latest_ratchet_time = 0
self._reload_ratchets(ratchets_path)
# TODO: Remove at some point
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG)
return True
else:
raise ValueError("No ratchet file path specified for "+str(self))
def enforce_ratchets(self):
"""
When ratchet enforcement is enabled, this destination will never accept packets that use its
base Identity key for encryption, but only accept packets encrypted with one of the retained
ratchet keys.
"""
if self.ratchets != None:
self.__enforce_ratchets = True
RNS.log("Ratchets enforced on "+str(self), RNS.LOG_DEBUG)
return True
else:
return False
def set_retained_ratchets(self, retained_ratchets):
"""
Sets the number of previously generated ratchet keys this destination will retain,
and try to use when decrypting incoming packets. Defaults to ``Destination.RATCHET_COUNT``.
:param retained_ratchets: The number of generated ratchets to retain.
:returns: True if the operation succeeded, False if not.
"""
if isinstance(retained_ratchets, int) and retained_ratchets > 0:
self.retained_ratchets = retained_ratchets
self._clean_ratchets()
return True
else:
return False
def set_ratchet_interval(self, interval):
"""
Sets the minimum interval in seconds between ratchet key rotation.
Defaults to ``Destination.RATCHET_INTERVAL``.
:param interval: The minimum interval in seconds.
:returns: True if the operation succeeded, False if not.
"""
if isinstance(interval, int) and interval > 0:
self.ratchet_interval = interval
return True
else:
return False
def create_keys(self):
"""
For a ``RNS.Destination.GROUP`` type destination, creates a new symmetric key.
@@ -374,7 +528,6 @@ class Destination:
self.prv_bytes = Fernet.generate_key()
self.prv = Fernet(self.prv_bytes)
def get_private_key(self):
"""
For a ``RNS.Destination.GROUP`` type destination, returns the symmetric private key.
@@ -388,7 +541,6 @@ class Destination:
else:
return self.prv_bytes
def load_private_key(self, key):
"""
For a ``RNS.Destination.GROUP`` type destination, loads a symmetric private key.
@@ -412,7 +564,6 @@ class Destination:
else:
raise TypeError("A single destination holds keys through an Identity instance")
def encrypt(self, plaintext):
"""
Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
@@ -424,7 +575,10 @@ class Destination:
return plaintext
if self.type == Destination.SINGLE and self.identity != None:
return self.identity.encrypt(plaintext)
selected_ratchet = RNS.Identity.get_ratchet(self.hash)
if selected_ratchet:
self.latest_ratchet_id = RNS.Identity._get_ratchet_id(selected_ratchet)
return self.identity.encrypt(plaintext, ratchet=selected_ratchet)
if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None:
@@ -436,8 +590,6 @@ class Destination:
else:
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
def decrypt(self, ciphertext):
"""
Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
@@ -449,7 +601,28 @@ class Destination:
return ciphertext
if self.type == Destination.SINGLE and self.identity != None:
return self.identity.decrypt(ciphertext)
if self.ratchets:
decrypted = None
try:
decrypted = self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
except:
decrypted = None
if not decrypted:
try:
RNS.log(f"Decryption with ratchets failed on {self}, reloading ratchets from storage and retrying", RNS.LOG_ERROR)
self._reload_ratchets(self.ratchets_path)
decrypted = self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
except Exception as e:
RNS.log(f"Decryption still failing after ratchet reload. The contained exception was: {e}", RNS.LOG_ERROR)
raise e
RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
return decrypted
else:
return self.identity.decrypt(ciphertext, ratchets=None, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None:
@@ -461,7 +634,6 @@ class Destination:
else:
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
def sign(self, message):
"""
Signs information for ``RNS.Destination.SINGLE`` type destination.
+229 -33
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -26,6 +26,7 @@ import RNS
import time
import atexit
import hashlib
import threading
from .vendor import umsgpack as umsgpack
@@ -49,8 +50,20 @@ class Identity:
KEYSIZE = 256*2
"""
X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
"""
X.25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
"""
RATCHETSIZE = 256
"""
X.25519 ratchet key size in bits.
"""
RATCHET_EXPIRY = 60*60*24*30
"""
The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
announced ratchet, and remember it for up to ``RATCHET_EXPIRY`` since receiving it, after which it will be discarded.
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.
"""
# Non-configurable constants
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
@@ -67,6 +80,9 @@ class Identity:
# Storage
known_destinations = {}
known_ratchets = {}
ratchet_persist_lock = threading.Lock()
@staticmethod
def remember(packet_hash, destination_hash, public_key, app_data = None):
@@ -145,9 +161,12 @@ class Identity:
except:
pass
for destination_hash in storage_known_destinations:
if not destination_hash in Identity.known_destinations:
Identity.known_destinations[destination_hash] = storage_known_destinations[destination_hash]
try:
for destination_hash in storage_known_destinations:
if not destination_hash in Identity.known_destinations:
Identity.known_destinations[destination_hash] = storage_known_destinations[destination_hash]
except Exception as e:
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_DEBUG)
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
@@ -164,6 +183,7 @@ class Identity:
except Exception as e:
RNS.log("Error while saving known destinations to disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
Identity.saving_known_destinations = False
@@ -181,7 +201,8 @@ class Identity:
Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
except:
except Exception as e:
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
else:
RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
@@ -192,7 +213,7 @@ class Identity:
Get a SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: SHA-256 hash as *bytes*
:returns: SHA-256 hash as *bytes*.
"""
return RNS.Cryptography.sha256(data)
@@ -202,7 +223,7 @@ class Identity:
Get a truncated SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash as *bytes*
:returns: Truncated SHA-256 hash as *bytes*.
"""
return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
@@ -212,24 +233,158 @@ class Identity:
Get a random SHA-256 hash.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash of random data as *bytes*
:returns: Truncated SHA-256 hash of random data as *bytes*.
"""
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
@staticmethod
def current_ratchet_id(destination_hash):
"""
Get the ID of the currently used ratchet key for a given destination hash
:param destination_hash: A destination hash as *bytes*.
:returns: A ratchet ID as *bytes* or *None*.
"""
ratchet = Identity.get_ratchet(destination_hash)
if ratchet == None:
return None
else:
return Identity._get_ratchet_id(ratchet)
@staticmethod
def _get_ratchet_id(ratchet_pub_bytes):
return Identity.full_hash(ratchet_pub_bytes)[:Identity.NAME_HASH_LENGTH//8]
@staticmethod
def _ratchet_public_bytes(ratchet):
return X25519PrivateKey.from_private_bytes(ratchet).public_key().public_bytes()
@staticmethod
def _generate_ratchet():
ratchet_prv = X25519PrivateKey.generate()
ratchet_pub = ratchet_prv.public_key()
return ratchet_prv.private_bytes()
@staticmethod
def _remember_ratchet(destination_hash, ratchet):
# TODO: Remove at some point, and only log new ratchets
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity._get_ratchet_id(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_EXTREME)
try:
Identity.known_ratchets[destination_hash] = ratchet
if not RNS.Transport.owner.is_connected_to_shared_instance:
def persist_job():
with Identity.ratchet_persist_lock:
hexhash = RNS.hexrep(destination_hash, delimit=False)
ratchet_data = {"ratchet": ratchet, "received": time.time()}
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
if not os.path.isdir(ratchetdir):
os.makedirs(ratchetdir)
outpath = f"{ratchetdir}/{hexhash}.out"
finalpath = f"{ratchetdir}/{hexhash}"
ratchet_file = open(outpath, "wb")
ratchet_file.write(umsgpack.packb(ratchet_data))
ratchet_file.close()
os.replace(outpath, finalpath)
threading.Thread(target=persist_job, daemon=True).start()
except Exception as e:
RNS.log(f"Could not persist ratchet for {RNS.prettyhexrep(destination_hash)} to storage.", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}")
RNS.trace_exception(e)
@staticmethod
def _clean_ratchets():
RNS.log("Cleaning ratchets...", RNS.LOG_DEBUG)
try:
now = time.time()
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
if os.path.isdir(ratchetdir):
for filename in os.listdir(ratchetdir):
try:
expired = False
with open(f"{ratchetdir}/{filename}", "rb") as rf:
ratchet_data = umsgpack.unpackb(rf.read())
if now > ratchet_data["received"]+Identity.RATCHET_EXPIRY:
expired = True
if expired:
os.unlink(f"{ratchetdir}/{filename}")
except Exception as e:
RNS.log(f"An error occurred while cleaning ratchets, in the processing of {ratchetdir}/{filename}.", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
except Exception as e:
RNS.log(f"An error occurred while cleaning ratchets. The contained exception was: {e}", RNS.LOG_ERROR)
@staticmethod
def get_ratchet(destination_hash):
if not destination_hash in Identity.known_ratchets:
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
hexhash = RNS.hexrep(destination_hash, delimit=False)
ratchet_path = f"{ratchetdir}/{hexhash}"
if os.path.isfile(ratchet_path):
try:
ratchet_file = open(ratchet_path, "rb")
ratchet_data = umsgpack.unpackb(ratchet_file.read())
if time.time() < ratchet_data["received"]+Identity.RATCHET_EXPIRY and len(ratchet_data["ratchet"]) == Identity.RATCHETSIZE//8:
Identity.known_ratchets[destination_hash] = ratchet_data["ratchet"]
else:
return None
except Exception as e:
RNS.log(f"An error occurred while loading ratchet data for {RNS.prettyhexrep(destination_hash)} from storage.", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
return None
if destination_hash in Identity.known_ratchets:
return Identity.known_ratchets[destination_hash]
else:
RNS.log(f"Could not load ratchet for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG)
return None
@staticmethod
def validate_announce(packet, only_validate_signature=False):
try:
if packet.packet_type == RNS.Packet.ANNOUNCE:
keysize = Identity.KEYSIZE//8
ratchetsize = Identity.RATCHETSIZE//8
name_hash_len = Identity.NAME_HASH_LENGTH//8
sig_len = Identity.SIGLENGTH//8
destination_hash = packet.destination_hash
public_key = packet.data[:Identity.KEYSIZE//8]
name_hash = packet.data[Identity.KEYSIZE//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8]
random_hash = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10]
signature = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8]
app_data = b""
if len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:]
signed_data = destination_hash+public_key+name_hash+random_hash+app_data
# Get public key bytes from announce
public_key = packet.data[:keysize]
# If the packet context flag is set,
# this announce contains a new ratchet
if packet.context_flag == RNS.Packet.FLAG_SET:
name_hash = packet.data[keysize:keysize+name_hash_len ]
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
ratchet = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+ratchetsize]
signature = packet.data[keysize+name_hash_len+10+ratchetsize:keysize+name_hash_len+10+ratchetsize+sig_len]
app_data = b""
if len(packet.data) > keysize+name_hash_len+10+sig_len+ratchetsize:
app_data = packet.data[keysize+name_hash_len+10+sig_len+ratchetsize:]
# If the packet context flag is not set,
# this announce does not contain a ratchet
else:
ratchet = b""
name_hash = packet.data[keysize:keysize+name_hash_len]
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
signature = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+sig_len]
app_data = b""
if len(packet.data) > keysize+name_hash_len+10+sig_len:
app_data = packet.data[keysize+name_hash_len+10+sig_len:]
signed_data = destination_hash+public_key+name_hash+random_hash+ratchet+app_data
if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = None
@@ -276,6 +431,9 @@ class Identity:
else:
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME)
if ratchet:
Identity._remember_ratchet(destination_hash, ratchet)
return True
else:
@@ -464,7 +622,7 @@ class Identity:
def get_context(self):
return None
def encrypt(self, plaintext):
def encrypt(self, plaintext, ratchet=None):
"""
Encrypts information for the identity.
@@ -476,7 +634,12 @@ class Identity:
ephemeral_key = X25519PrivateKey.generate()
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
shared_key = ephemeral_key.exchange(self.pub)
if ratchet != None:
target_public_key = X25519PublicKey.from_public_bytes(ratchet)
else:
target_public_key = self.pub
shared_key = ephemeral_key.exchange(target_public_key)
derived_key = RNS.Cryptography.hkdf(
length=32,
@@ -494,7 +657,7 @@ class Identity:
raise KeyError("Encryption failed because identity does not hold a public key")
def decrypt(self, ciphertext_token):
def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False, ratchet_id_receiver=None):
"""
Decrypts information for the identity.
@@ -508,22 +671,55 @@ class Identity:
try:
peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
shared_key = self.prv.exchange(peer_pub)
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context(),
)
fernet = Fernet(derived_key)
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
plaintext = fernet.decrypt(ciphertext)
if ratchets:
for ratchet in ratchets:
try:
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet)
ratchet_id = Identity._get_ratchet_id(ratchet_prv.public_key().public_bytes())
shared_key = ratchet_prv.exchange(peer_pub)
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context(),
)
fernet = Fernet(derived_key)
plaintext = fernet.decrypt(ciphertext)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = ratchet_id
break
except Exception as e:
pass
if enforce_ratchets and plaintext == None:
RNS.log("Decryption with ratchet enforcement by "+RNS.prettyhexrep(self.hash)+" failed. Dropping packet.", RNS.LOG_DEBUG)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
return None
if plaintext == None:
shared_key = self.prv.exchange(peer_pub)
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context(),
)
fernet = Fernet(derived_key)
plaintext = fernet.decrypt(ciphertext)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
except Exception as e:
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
return plaintext;
else:
+1 -1
View File
@@ -96,7 +96,7 @@ class AX25KISSInterface(Interface):
self.stopbits = stopbits
self.timeout = 100
self.online = False
self.bitrate = KISSInterface.BITRATE_GUESS
self.bitrate = AX25KISSInterface.BITRATE_GUESS
self.packet_queue = []
self.flow_control = flow_control
+503 -109
View File
@@ -28,6 +28,15 @@ import time
import math
import RNS
try:
from able import BluetoothDispatcher, GATT_SUCCESS
except Exception as e:
GATT_SUCCESS = 0x00
class BluetoothDispatcher():
def __init__(**kwargs):
RNS.log("Attempt to initialise BLE connectivity, but Android BLE support library is unavailable", RNS.LOG_ERROR)
raise OSError("No BLE support available")
class KISS():
FEND = 0xC0
FESC = 0xDB
@@ -54,11 +63,13 @@ class KISS():
CMD_STAT_SNR = 0x24
CMD_STAT_CHTM = 0x25
CMD_STAT_PHYPRM = 0x26
CMD_STAT_BAT = 0x27
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_FB_EXT = 0x41
CMD_FB_READ = 0x42
CMD_FB_WRITE = 0x43
CMD_BT_CTRL = 0x46
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50
@@ -76,10 +87,15 @@ class KISS():
ERROR_INITRADIO = 0x01
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
ERROR_QUEUE_FULL = 0x04
ERROR_MEMORY_LOW = 0x05
ERROR_MODEM_TIMEOUT = 0x06
ERROR_INVALID_FIRMWARE = 0x10
ERROR_INVALID_BLE_MTU = 0x20
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
PLATFORM_NRF52 = 0x70
@staticmethod
def escape(data):
@@ -88,6 +104,10 @@ class KISS():
return data
class AndroidBluetoothManager():
DEVICE_TYPE_CLASSIC = 1
DEVICE_TYPE_LE = 2
DEVICE_TYPE_DUAL = 3
def __init__(self, owner, target_device_name = None, target_device_address = None):
from jnius import autoclass
self.owner = owner
@@ -114,7 +134,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):
@@ -166,8 +186,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_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
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:
@@ -232,6 +252,15 @@ class RNodeInterface(Interface):
RECONNECT_WAIT = 5
PORT_IO_TIMEOUT = 3
Q_SNR_MIN_BASE = -9
Q_SNR_MAX = 6
Q_SNR_STEP = 2
BATTERY_STATE_UNKNOWN = 0x00
BATTERY_STATE_DISCHARGING = 0x01
BATTERY_STATE_CHARGING = 0x02
BATTERY_STATE_CHARGED = 0x03
@classmethod
def bluetooth_control(device_serial = None, port = None, enable_bluetooth = False, disable_bluetooth = False, pairing_mode = False):
if (port != None or device_serial != None) and (enable_bluetooth or disable_bluetooth or pairing_mode):
@@ -316,7 +345,8 @@ class RNodeInterface(Interface):
self, owner, name, port, frequency = None, bandwidth = None, txpower = None,
sf = None, cr = None, flow_control = False, id_interval = None,
allow_bluetooth = False, target_device_name = None,
target_device_address = None, id_callsign = None, st_alock = None, lt_alock = None):
target_device_address = None, id_callsign = None, st_alock = None, lt_alock = None,
ble_addr = None, ble_name = None, force_ble=False):
import importlib
if RNS.vendor.platformutils.is_android():
self.on_android = True
@@ -363,9 +393,19 @@ class RNodeInterface(Interface):
self.stopbits = 1
self.timeout = 150
self.online = False
self.detached = False
self.hw_errors = []
self.allow_bluetooth = allow_bluetooth
self.use_ble = False
self.ble_name = ble_name
self.ble_addr = ble_addr
self.ble = None
self.ble_rx_lock = threading.Lock()
self.ble_tx_lock = threading.Lock()
self.ble_rx_queue= b""
self.ble_tx_queue= b""
self.frequency = frequency
self.bandwidth = bandwidth
self.txpower = txpower
@@ -386,6 +426,7 @@ class RNodeInterface(Interface):
self.last_id = 0
self.first_tx = None
self.reconnect_w = RNodeInterface.RECONNECT_WAIT
self.reconnect_lock = threading.Lock()
self.r_frequency = None
self.r_bandwidth = None
@@ -409,6 +450,8 @@ class RNodeInterface(Interface):
self.r_symbol_rate = None
self.r_preamble_symbols = None
self.r_premable_time_ms = None
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
self.r_battery_percent = 0
self.packet_queue = []
self.flow_control = flow_control
@@ -418,12 +461,15 @@ class RNodeInterface(Interface):
self.port_io_timeout = RNodeInterface.PORT_IO_TIMEOUT
self.last_imagedata = None
if force_ble or self.ble_addr != None or self.ble_name != None:
self.use_ble = True
self.validcfg = True
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.txpower < 0 or self.txpower > 17):
if (self.txpower < 0 or self.txpower > 22):
RNS.log("Invalid TX power configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
@@ -431,7 +477,7 @@ class RNodeInterface(Interface):
RNS.log("Invalid bandwidth configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.sf < 7 or self.sf > 12):
if (self.sf < 5 or self.sf > 12):
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
@@ -483,9 +529,7 @@ class RNodeInterface(Interface):
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
if len(self.hw_errors) == 0:
RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
thread = threading.Thread(target=self.reconnect_port)
thread.daemon = True
thread.start()
thread = threading.Thread(target=self.reconnect_port, daemon=True).start()
def read_mux(self, len=None):
@@ -509,96 +553,127 @@ class RNodeInterface(Interface):
else:
raise IOError("No ports available for writing")
# def reset_ble(self):
# RNS.log(f"Clearing previous connection instance: "+str(self.ble))
# del self.ble
# self.ble = None
# self.serial = None
# self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
# self.serial = self.ble
# RNS.log(f"New connection instance: "+str(self.ble))
def open_port(self):
if self.port != None:
RNS.log("Opening serial port "+self.port+"...")
# Get device parameters
from usb4a import usb
device = usb.get_usb_device(self.port)
if device:
vid = device.getVendorId()
pid = device.getProductId()
if not self.use_ble:
if self.port != None:
RNS.log("Opening serial port "+self.port+"...")
# Get device parameters
from usb4a import usb
device = usb.get_usb_device(self.port)
if device:
vid = device.getVendorId()
pid = device.getProductId()
# Driver overrides for speficic chips
proxy = self.pyserial.get_serial_port
if vid == 0x1A86 and pid == 0x55D4:
# Force CDC driver for Qinheng CH34x
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
from usbserial4a.cdcacmserial4a import CdcAcmSerial
proxy = CdcAcmSerial
# Driver overrides for speficic chips
proxy = self.pyserial.get_serial_port
if vid == 0x1A86 and pid == 0x55D4:
# Force CDC driver for Qinheng CH34x
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
from usbserial4a.cdcacmserial4a import CdcAcmSerial
proxy = CdcAcmSerial
self.serial = proxy(
self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = None,
inter_byte_timeout = None,
# write_timeout = wtimeout,
dsrdtr = False,
)
self.serial = proxy(
self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = None,
inter_byte_timeout = None,
# write_timeout = wtimeout,
dsrdtr = False,
)
if vid == 0x0403:
# Hardware parameters for FTDI devices @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1
elif vid == 0x10C4:
# Hardware parameters for SiLabs CP210x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.012
elif vid == 0x1A86 and pid == 0x55D4:
# Hardware parameters for Qinheng CH34x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.1
else:
# Default values
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1
if vid == 0x0403:
# Hardware parameters for FTDI devices @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1
elif vid == 0x10C4:
# Hardware parameters for SiLabs CP210x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.012
elif vid == 0x1A86 and pid == 0x55D4:
# Hardware parameters for Qinheng CH34x @ 115200 baud
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
self.serial.USB_READ_TIMEOUT_MILLIS = 12
self.serial.timeout = 0.1
else:
# Default values
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
self.serial.USB_READ_TIMEOUT_MILLIS = 100
self.serial.timeout = 0.1
RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
elif self.allow_bluetooth:
if self.bt_manager == None:
self.bt_manager = AndroidBluetoothManager(
owner = self,
target_device_name = self.bt_target_device_name,
target_device_address = self.bt_target_device_address
)
elif self.allow_bluetooth:
if self.bt_manager == None:
self.bt_manager = AndroidBluetoothManager(
owner = self,
target_device_name = self.bt_target_device_name,
target_device_address = self.bt_target_device_address
)
if self.bt_manager != None:
self.bt_manager.connect_any_device()
if self.bt_manager != None:
self.bt_manager.connect_any_device()
else:
if self.ble == None:
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
self.serial = self.ble
open_time = time.time()
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
time.sleep(1)
def configure_device(self):
self.resetRadioState()
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.daemon = True
thread.start()
thread = threading.Thread(target=self.readLoop, daemon=True).start()
self.detect()
sleep(0.5)
if not self.use_ble:
sleep(0.5)
else:
ble_detect_timeout = 5
detect_time = time.time()
while not self.detected and time.time() < detect_time + ble_detect_timeout:
time.sleep(0.1)
if self.detected:
detect_time = RNS.prettytime(time.time()-detect_time)
else:
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
if not self.detected:
raise IOError("Could not detect device")
else:
if self.platform == KISS.PLATFORM_ESP32:
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
self.display = True
if not self.firmware_ok:
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)
@@ -646,6 +721,9 @@ class RNodeInterface(Interface):
self.setRadioState(KISS.RADIO_STATE_ON)
time.sleep(0.15)
if self.use_ble:
time.sleep(1)
def detect(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
written = self.write_mux(kiss_command)
@@ -799,16 +877,19 @@ class RNodeInterface(Interface):
raise IOError("An IO error occurred while configuring radio state for "+str(self))
def validate_firmware(self):
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
self.firmware_ok = True
if (self.maj_version > RNodeInterface.REQUIRED_FW_VER_MAJ):
self.firmware_ok = True
else:
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
self.firmware_ok = True
if self.firmware_ok:
return
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/rnodeconfigutil/")
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/reticulum/")
error_description = "The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version)+". "
error_description += "This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN)+". "
error_description += "Please update your RNode firmware with rnodeconf from: https://github.com/markqvist/rnodeconfigutil/"
@@ -816,7 +897,7 @@ class RNodeInterface(Interface):
def validateRadioState(self):
RNS.log("Wating for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
if not self.platform == KISS.PLATFORM_ESP32:
sleep(1.00);
else:
@@ -844,6 +925,13 @@ class RNodeInterface(Interface):
else:
return False
def resetRadioState(self):
self.r_frequency = None
self.r_bandwidth = None
self.r_txpower = None
self.r_sf = None
self.r_cr = None
self.r_state = None
def updateBitrate(self):
try:
@@ -860,9 +948,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)
@@ -1044,6 +1129,19 @@ 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
@@ -1122,6 +1220,25 @@ class RNodeInterface(Interface):
RNS.log(str(self)+" Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms (at "+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
RNS.log(str(self)+" Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
RNS.log(str(self)+" Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
elif (command == KISS.CMD_STAT_BAT):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 2):
bat_percent = command_buffer[1]
if bat_percent > 100:
bat_percent = 100
if bat_percent < 0:
bat_percent = 0
self.r_battery_state = command_buffer[0]
self.r_battery_percent = bat_percent
elif (command == KISS.CMD_RANDOM):
self.r_random = byte
elif (command == KISS.CMD_PLATFORM):
@@ -1135,6 +1252,12 @@ class RNodeInterface(Interface):
elif (byte == KISS.ERROR_TXFAILED):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Hardware transmit failure")
elif (byte == KISS.ERROR_MEMORY_LOW):
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Memory exhausted", RNS.LOG_ERROR)
self.hw_errors.append({"error": KISS.ERROR_MEMORY_LOW, "description": "Memory exhausted on connected device"})
elif (byte == KISS.ERROR_MODEM_TIMEOUT):
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Modem communication timed out", RNS.LOG_ERROR)
self.hw_errors.append({"error": KISS.ERROR_MODEM_TIMEOUT, "description": "Modem communication timed out on connected device"})
else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Unknown hardware failure")
@@ -1155,7 +1278,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
@@ -1194,48 +1317,319 @@ class RNodeInterface(Interface):
if self.bt_manager != None:
self.bt_manager.close()
self.reconnect_port()
if not self.detached:
self.reconnect_port()
def reconnect_port(self):
while not self.online and len(self.hw_errors) == 0:
try:
time.sleep(self.reconnect_w)
if self.serial != None and self.port != None:
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_EXTREME)
if self.reconnect_lock.locked():
RNS.log("Dropping superflous reconnect port job")
return
if self.bt_manager != None:
RNS.log("Attempting to reconnect Bluetooth device for "+str(self)+"...", RNS.LOG_EXTREME)
with self.reconnect_lock:
while not self.online and len(self.hw_errors) == 0:
try:
time.sleep(self.reconnect_w)
if self.serial != None and self.port != None:
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_EXTREME)
self.open_port()
if self.bt_manager != None:
RNS.log("Attempting to reconnect Bluetooth device for "+str(self)+"...", RNS.LOG_EXTREME)
if hasattr(self, "serial") and self.serial != None and self.serial.is_open:
self.configure_device()
if self.online:
if self.last_imagedata != None:
self.display_image(self.last_imagedata)
self.enable_external_framebuffer()
elif hasattr(self, "bt_manager") and self.bt_manager != None and self.bt_manager.connected:
self.configure_device()
if self.online:
if self.last_imagedata != None:
self.display_image(self.last_imagedata)
self.enable_external_framebuffer()
self.open_port()
except Exception as e:
RNS.log("Error while reconnecting RNode, the contained exception was: "+str(e), RNS.LOG_ERROR)
if hasattr(self, "serial") and self.serial != None and self.serial.is_open:
self.configure_device()
if self.online:
if self.last_imagedata != None:
self.display_image(self.last_imagedata)
self.enable_external_framebuffer()
elif hasattr(self, "bt_manager") and self.bt_manager != None and self.bt_manager.connected:
self.configure_device()
if self.online:
if self.last_imagedata != None:
self.display_image(self.last_imagedata)
self.enable_external_framebuffer()
if self.online:
RNS.log("Reconnected serial port for "+str(self))
except Exception as e:
RNS.log("Error while reconnecting RNode, the contained exception was: "+str(e), RNS.LOG_ERROR)
if self.online:
RNS.log("Reconnected serial port for "+str(self))
def detach(self):
self.detached = True
self.disable_external_framebuffer()
self.setRadioState(KISS.RADIO_STATE_OFF)
self.leave()
if self.use_ble:
self.ble.close()
def should_ingress_limit(self):
return False
def get_battery_state(self):
return self.r_battery_state
def get_battery_state_string(self):
if self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGED:
return "charged"
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGING:
return "charging"
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_DISCHARGING:
return "discharging"
else:
return "unknown"
def get_battery_percent(self):
return self.r_battery_percent
def ble_receive(self, data):
with self.ble_rx_lock:
self.ble_rx_queue += data
def ble_waiting(self):
return len(self.ble_tx_queue) > 0
def get_ble_waiting(self, n):
with self.ble_tx_lock:
data = self.ble_tx_queue[:n]
self.ble_tx_queue = self.ble_tx_queue[n:]
return data
def __str__(self):
return "RNodeInterface["+str(self.name)+"]"
class BLEConnection(BluetoothDispatcher):
UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
UART_RX_CHAR_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
UART_TX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
MAX_GATT_ATTR_LEN = 512
BASE_MTU = 20
TARGET_MTU = 512
MTU_TIMEOUT = 4.0
CONNECT_TIMEOUT = 7.0
RECONNECT_WAIT = 1.0
@property
def is_open(self):
return self.connected
@property
def in_waiting(self):
return len(self.owner.ble_rx_queue) > 0
def write(self, data_bytes):
with self.owner.ble_tx_lock:
self.owner.ble_tx_queue += data_bytes
return len(data_bytes)
def read(self):
with self.owner.ble_rx_lock:
data = self.owner.ble_rx_queue
self.owner.ble_rx_queue = b""
return data
def close(self):
try:
if self.connected:
RNS.log(f"Disconnecting BLE device from {self.owner}", RNS.LOG_DEBUG)
# RNS.log("Waiting for BLE write buffer to empty...")
timeout = time.time() + 10
while self.owner.ble_waiting() and self.write_thread != None and time.time() < timeout:
time.sleep(0.1)
# if time.time() > timeout:
# RNS.log("Writing timed out")
# else:
# RNS.log("Writing concluded")
self.rx_char = None
self.tx_char = None
self.mtu = BLEConnection.BASE_MTU
self.mtu_requested_time = None
if self.write_thread != None:
# RNS.log("Waiting for write thread to finish...")
while self.write_thread != None:
time.sleep(0.1)
# RNS.log("Writing finished, closing GATT connection")
self.close_gatt()
with self.owner.ble_rx_lock:
self.owner.ble_rx_queue = b""
with self.owner.ble_tx_lock:
self.owner.ble_tx_queue = b""
self.connected = False
self.ble_device = None
except Exception as e:
RNS.log("An error occurred while closing BLE connection for {self.owner}: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
def __init__(self, owner=None, target_name=None, target_bt_addr=None):
super(BLEConnection, self).__init__()
self.owner = owner
self.target_name = target_name
self.target_bt_addr = target_bt_addr
self.connect_timeout = BLEConnection.CONNECT_TIMEOUT
self.ble_device = None
self.rx_char = None
self.tx_char = None
self.connected = False
self.was_connected = False
self.connected_time = None
self.mtu_requested_time = None
self.running = False
self.should_run = False
self.connect_job_running = False
self.write_thread = None
self.mtu = BLEConnection.BASE_MTU
self.target_mtu = BLEConnection.TARGET_MTU
self.bt_manager = AndroidBluetoothManager(owner=self)
self.should_run = True
self.connection_thread = threading.Thread(target=self.connection_job, daemon=True).start()
def write_loop(self):
try:
while self.connected and self.rx_char != None:
if self.owner.ble_waiting():
data = self.owner.get_ble_waiting(self.mtu)
self.write_characteristic(self.rx_char, data)
else:
time.sleep(0.1)
except Exception as e:
RNS.log("An error occurred in {self} write loop: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
self.write_thread = None
def connection_job(self):
while self.should_run:
if self.bt_manager.bt_enabled():
if self.ble_device == None:
self.ble_device = self.find_target_device()
if self.ble_device != None:
if not self.connected:
if self.was_connected:
RNS.log(f"Throttling BLE reconnect for {BLEConnection.RECONNECT_WAIT} seconds", RNS.LOG_DEBUG)
time.sleep(BLEConnection.RECONNECT_WAIT)
self.connect_device()
else:
if self.connected:
RNS.log("Bluetooth was disabled, closing active BLE device connection", RNS.LOG_ERROR)
self.close()
time.sleep(2)
def connect_device(self):
if self.ble_device != None and self.bt_manager.bt_enabled():
RNS.log(f"Trying to connect BLE device {self.ble_device.getName()} / {self.ble_device.getAddress()} for {self.owner}...", RNS.LOG_DEBUG)
self.mtu = BLEConnection.BASE_MTU
self.connect_by_device_address(self.ble_device.getAddress())
end = time.time() + BLEConnection.CONNECT_TIMEOUT
while time.time() < end and not self.connected:
time.sleep(0.25)
if self.connected:
self.owner.port = f"ble://{self.ble_device.getAddress()}"
self.write_thread = threading.Thread(target=self.write_loop, daemon=True)
self.write_thread.start()
else:
RNS.log(f"BLE device connection timed out for {self.owner}", RNS.LOG_DEBUG)
if self.mtu_requested_time:
RNS.log("MTU update timeout, tearing down connection")
self.owner.hw_errors.append({"error": KISS.ERROR_INVALID_BLE_MTU, "description": "The Bluetooth Low Energy transfer MTU could not be configured for the connected device, and communication has failed. Restart Reticulum and any connected applications to retry connecting."})
self.close()
self.should_run = False
self.close_gatt()
self.connect_job_running = False
def device_disconnected(self):
RNS.log(f"BLE device for {self.owner} disconnected", RNS.LOG_NOTICE)
self.connected = False
self.ble_device = None
self.close_gatt()
def find_target_device(self):
found_device = None
potential_devices = self.bt_manager.get_paired_devices()
if self.target_bt_addr != None:
for device in potential_devices:
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
if str(device.getAddress()).replace(":", "").lower() == str(self.target_bt_addr).replace(":", "").lower():
found_device = device
break
if not found_device and self.target_name != None:
for device in potential_devices:
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
if device.getName().lower() == self.target_name.lower():
found_device = device
break
if not found_device:
for device in potential_devices:
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
if device.getName().startswith("RNode "):
found_device = device
break
return found_device
def on_connection_state_change(self, status, state):
if status == GATT_SUCCESS and state:
self.discover_services()
else:
self.device_disconnected()
def on_services(self, status, services):
if status == GATT_SUCCESS:
self.rx_char = services.search(BLEConnection.UART_RX_CHAR_UUID)
if self.rx_char is not None:
self.tx_char = services.search(BLEConnection.UART_TX_CHAR_UUID)
if self.tx_char is not None:
if self.enable_notifications(self.tx_char):
RNS.log("Enabled notifications for BLE TX characteristic", RNS.LOG_DEBUG)
RNS.log(f"Requesting BLE connection MTU update to {self.target_mtu}", RNS.LOG_DEBUG)
self.mtu_requested_time = time.time()
self.request_mtu(self.target_mtu)
else:
RNS.log("Could not enable notifications for BLE TX characteristic", RNS.LOG_ERROR)
else:
RNS.log("BLE device service discovery failure", RNS.LOG_ERROR)
def on_mtu_changed(self, mtu, status):
if status == GATT_SUCCESS:
self.mtu = min(mtu-5, BLEConnection.MAX_GATT_ATTR_LEN)
RNS.log(f"BLE MTU updated to {self.mtu} for {self.owner}", RNS.LOG_DEBUG)
self.connected = True
self.was_connected = True
self.connected_time = time.time()
self.mtu_requested_time = None
else:
RNS.log(f"MTU update request did not succeed, mtu={mtu}, status={status}", RNS.LOG_ERROR)
def on_characteristic_changed(self, characteristic):
if characteristic.getUuid().toString() == BLEConnection.UART_TX_CHAR_UUID:
recvd = bytes(characteristic.getValue())
self.owner.ble_receive(recvd)
+106 -62
View File
@@ -43,6 +43,9 @@ class AutoInterface(Interface):
SCOPE_ORGANISATION = "8"
SCOPE_GLOBAL = "e"
MULTICAST_PERMANENT_ADDRESS_TYPE = "0"
MULTICAST_TEMPORARY_ADDRESS_TYPE = "1"
PEERING_TIMEOUT = 7.5
ALL_IGNORE_IFS = ["lo0"]
@@ -74,7 +77,16 @@ class AutoInterface(Interface):
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):
def interface_name_to_index(self, ifname):
# socket.if_nametoindex doesn't work with uuid interface names on windows, it wants the ethernet_0 style
# we will just get the index from netinfo instead as it seems to work
if RNS.vendor.platformutils.is_windows():
return self.netinfo.interface_names_to_indexes()[ifname]
return socket.if_nametoindex(ifname)
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, multicast_address_type=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
from RNS.vendor.ifaddr import niwrapper
super().__init__()
self.netinfo = niwrapper
@@ -128,6 +140,15 @@ class AutoInterface(Interface):
else:
self.discovery_port = discovery_port
if multicast_address_type == None:
self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE
elif str(multicast_address_type).lower() == "temporary":
self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE
elif str(multicast_address_type).lower() == "permanent":
self.multicast_address_type = AutoInterface.MULTICAST_PERMANENT_ADDRESS_TYPE
else:
self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE
if data_port == None:
self.data_port = AutoInterface.DEFAULT_DATA_PORT
else:
@@ -156,73 +177,94 @@ class AutoInterface(Interface):
gt += ":"+"{:02x}".format(g[9]+(g[8]<<8))
gt += ":"+"{:02x}".format(g[11]+(g[10]<<8))
gt += ":"+"{:02x}".format(g[13]+(g[12]<<8))
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
self.mcast_discovery_address = "ff"+self.multicast_address_type+self.discovery_scope+":"+gt
suitable_interfaces = 0
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":
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces:
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)
try:
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":
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces:
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:
addresses = self.list_addresses(ifname)
if self.netinfo.AF_INET6 in addresses:
link_local_addr = None
for address in addresses[self.netinfo.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
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)
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.list_addresses(ifname)
if self.netinfo.AF_INET6 in addresses:
link_local_addr = None
for address in addresses[self.netinfo.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
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()
nice_name = self.netinfo.interface_name_to_nice_name(ifname)
if nice_name != None and nice_name != ifname:
RNS.log(f"{self} Selecting link-local address {link_local_addr} for interface {nice_name} / {ifname}", RNS.LOG_EXTREME)
else:
RNS.log(f"{self} Selecting link-local address {link_local_addr} for interface {ifname}", RNS.LOG_EXTREME)
if link_local_addr == None:
RNS.log(str(self)+" No link-local IPv6 address configured for "+str(ifname)+", skipping interface", RNS.LOG_EXTREME)
else:
mcast_addr = self.mcast_discovery_address
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME)
# Struct with interface index
if_struct = struct.pack("I", socket.if_nametoindex(ifname))
# Set up multicast socket
discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, "SO_REUSEPORT"):
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
# Join multicast group
mcast_group = socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
# Bind socket
if self.discovery_scope == AutoInterface.SCOPE_LINK:
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
if link_local_addr == None:
RNS.log(str(self)+" No link-local IPv6 address configured for "+str(ifname)+", skipping interface", RNS.LOG_EXTREME)
else:
addr_info = socket.getaddrinfo(mcast_addr, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
mcast_addr = self.mcast_discovery_address
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME)
discovery_socket.bind(addr_info[0][4])
# Struct with interface index
if_struct = struct.pack("I", self.interface_name_to_index(ifname))
# Set up thread for discovery packets
def discovery_loop():
self.discovery_handler(discovery_socket, ifname)
# Set up multicast socket
discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, "SO_REUSEPORT"):
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
thread = threading.Thread(target=discovery_loop)
thread.daemon = True
thread.start()
# Join multicast group
mcast_group = socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
suitable_interfaces += 1
# Bind socket
if RNS.vendor.platformutils.is_windows():
# window throws "[WinError 10049] The requested address is not valid in its context"
# when trying to use the multicast address as host, or when providing interface index
# passing an empty host appears to work, but probably not exactly how we want it to...
discovery_socket.bind(('', self.discovery_port))
else:
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
def discovery_loop():
self.discovery_handler(discovery_socket, ifname)
thread = threading.Thread(target=discovery_loop)
thread.daemon = True
thread.start()
suitable_interfaces += 1
except Exception as e:
nice_name = self.netinfo.interface_name_to_nice_name(ifname)
if nice_name != None and nice_name != ifname:
RNS.log(f"Could not configure the system interface {nice_name} / {ifname} for use with {self}, skipping it. The contained exception was: {e}", RNS.LOG_ERROR)
else:
RNS.log(f"Could not configure the system interface {ifname} for use with {self}, skipping it. The contained exception was: {e}", RNS.LOG_ERROR)
if suitable_interfaces == 0:
RNS.log(str(self)+" could not autoconfigure. This interface currently provides no connectivity.", RNS.LOG_WARNING)
@@ -241,7 +283,7 @@ class AutoInterface(Interface):
socketserver.UDPServer.address_family = socket.AF_INET6
for ifname in self.adopted_interfaces:
local_addr = self.adopted_interfaces[ifname]+"%"+ifname
local_addr = self.adopted_interfaces[ifname]+"%"+str(self.interface_name_to_index(ifname))
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
address = addr_info[0][4]
@@ -334,6 +376,8 @@ class AutoInterface(Interface):
thread.daemon = True
thread.start()
self.carrier_changed = True
except Exception as e:
RNS.log("Could not get device information while updating link-local addresses for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -366,7 +410,7 @@ class AutoInterface(Interface):
announce_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
addr_info = socket.getaddrinfo(self.mcast_discovery_address, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
ifis = struct.pack("I", socket.if_nametoindex(ifname))
ifis = struct.pack("I", self.interface_name_to_index(ifname))
announce_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifis)
announce_socket.sendto(discovery_token, addr_info[0][4])
announce_socket.close()
@@ -419,8 +463,8 @@ class AutoInterface(Interface):
try:
if self.outbound_udp_socket == None:
self.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
peer_addr = str(peer)+"%"+str(self.peers[peer][0])
peer_addr = str(peer)+"%"+str(self.interface_name_to_index(self.peers[peer][0]))
addr_info = socket.getaddrinfo(peer_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
self.outbound_udp_socket.sendto(data, addr_info[0][4])
+2 -1
View File
@@ -42,7 +42,7 @@ class Interface:
# Which interface modes a Transport Node should
# actively discover paths for.
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY]
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY, MODE_ROAMING]
# How many samples to use for announce
# frequency calculations
@@ -68,6 +68,7 @@ class Interface:
self.txb = 0
self.created = time.time()
self.online = False
self.bitrate = 1e6
self.ingress_control = True
self.ic_max_held_announces = Interface.MAX_HELD_ANNOUNCES
+21 -7
View File
@@ -28,6 +28,7 @@ import time
import sys
import os
import RNS
from threading import Lock
class HDLC():
FLAG = 0x7E
@@ -50,7 +51,7 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
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):
super().__init__()
@@ -77,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
@@ -107,6 +109,7 @@ class LocalClientInterface(Interface):
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
@@ -139,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)
@@ -147,9 +153,6 @@ class LocalClientInterface(Interface):
def processIncoming(self, data):
if self._force_bitrate:
time.sleep(len(data) / self.bitrate * 8)
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
@@ -167,8 +170,16 @@ class LocalClientInterface(Interface):
if self.online:
try:
self.writing = True
if self._force_bitrate:
time.sleep(len(data) / self.bitrate * 8)
if not hasattr(self, "send_lock"):
self.send_lock = Lock()
with self.send_lock:
# RNS.log(f"Simulating latency of {RNS.prettytime(s)} for {len(data)} bytes", RNS.LOG_EXTREME)
s = len(data) / self.bitrate * 8
time.sleep(s)
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
self.socket.sendall(data)
self.writing = False
@@ -261,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)
@@ -330,6 +342,8 @@ class LocalServerInterface(Interface):
spawned_interface.target_port = str(handler.client_address[1])
spawned_interface.parent_interface = self
spawned_interface.bitrate = self.bitrate
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)
+346 -29
View File
@@ -54,11 +54,13 @@ class KISS():
CMD_STAT_SNR = 0x24
CMD_STAT_CHTM = 0x25
CMD_STAT_PHYPRM = 0x26
CMD_STAT_BAT = 0x27
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_FB_EXT = 0x41
CMD_FB_READ = 0x42
CMD_FB_WRITE = 0x43
CMD_BT_CTRL = 0x46
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50
@@ -76,9 +78,13 @@ class KISS():
ERROR_INITRADIO = 0x01
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
ERROR_QUEUE_FULL = 0x04
ERROR_MEMORY_LOW = 0x05
ERROR_MODEM_TIMEOUT = 0x06
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
PLATFORM_NRF52 = 0x70
@staticmethod
def escape(data):
@@ -91,7 +97,7 @@ class RNodeInterface(Interface):
MAX_CHUNK = 32768
FREQ_MIN = 137000000
FREQ_MAX = 1020000000
FREQ_MAX = 3000000000
RSSI_OFFSET = 157
@@ -102,9 +108,18 @@ 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, st_alock = None, lt_alock = None):
Q_SNR_MIN_BASE = -9
Q_SNR_MAX = 6
Q_SNR_STEP = 2
BATTERY_STATE_UNKNOWN = 0x00
BATTERY_STATE_DISCHARGING = 0x01
BATTERY_STATE_CHARGING = 0x02
BATTERY_STATE_CHARGED = 0x03
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None, st_alock = None, lt_alock = None, ble_addr = None, ble_name = None, force_ble=False):
if RNS.vendor.platformutils.is_android():
raise SystemError("Invlaid interface type. The Android-specific RNode interface must be used on Android")
raise SystemError("Invalid interface type. The Android-specific RNode interface must be used on Android")
import importlib
if importlib.util.find_spec('serial') != None:
@@ -131,6 +146,15 @@ class RNodeInterface(Interface):
self.detached = False
self.reconnecting= False
self.use_ble = False
self.ble_name = ble_name
self.ble_addr = ble_addr
self.ble = None
self.ble_rx_lock = threading.Lock()
self.ble_tx_lock = threading.Lock()
self.ble_rx_queue= b""
self.ble_tx_queue= b""
self.frequency = frequency
self.bandwidth = bandwidth
self.txpower = txpower
@@ -174,26 +198,31 @@ class RNodeInterface(Interface):
self.r_symbol_rate = None
self.r_preamble_symbols = None
self.r_premable_time_ms = None
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
self.r_battery_percent = 0
self.packet_queue = []
self.flow_control = flow_control
self.interface_ready = False
self.announce_rate_target = None
if force_ble or self.ble_addr != None or self.ble_name != None:
self.use_ble = True
self.validcfg = True
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.txpower < 0 or self.txpower > 17):
if (self.txpower < 0 or self.txpower > 22):
RNS.log("Invalid TX power configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.bandwidth < 7800 or self.bandwidth > 500000):
if (self.bandwidth < 7800 or self.bandwidth > 1625000):
RNS.log("Invalid bandwidth configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.sf < 7 or self.sf > 12):
if (self.sf < 5 or self.sf > 12):
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
@@ -243,23 +272,38 @@ class RNodeInterface(Interface):
def open_port(self):
RNS.log("Opening serial port "+self.port+"...")
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.pyserial.PARITY_NONE,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
if not self.use_ble:
RNS.log("Opening serial port "+self.port+"...")
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.pyserial.PARITY_NONE,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
else:
RNS.log(f"Opening BLE connection for {self}...")
if self.ble != None and self.ble.running == False:
self.ble.close()
self.ble.cleanup()
self.ble = None
if self.ble == None:
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
self.serial = self.ble
def configure_device(self):
open_time = time.time()
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
time.sleep(1)
def reset_radio_state(self):
self.r_frequency = None
self.r_bandwidth = None
self.r_txpower = None
@@ -267,6 +311,10 @@ class RNodeInterface(Interface):
self.r_cr = None
self.r_state = None
self.r_lock = None
self.detected = False
def configure_device(self):
self.reset_radio_state()
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
@@ -274,13 +322,23 @@ class RNodeInterface(Interface):
thread.start()
self.detect()
sleep(0.2)
if not self.use_ble:
sleep(0.2)
else:
ble_detect_timeout = 5
detect_time = time.time()
while not self.detected and time.time() < detect_time + ble_detect_timeout:
time.sleep(0.1)
if self.detected:
detect_time = RNS.prettytime(time.time()-detect_time)
else:
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
if not self.detected:
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
self.serial.close()
else:
if self.platform == KISS.PLATFORM_ESP32:
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
self.display = True
RNS.log("Serial port "+self.port+" is now open")
@@ -308,6 +366,9 @@ class RNodeInterface(Interface):
self.setLTALock()
self.setRadioState(KISS.RADIO_STATE_ON)
if self.use_ble:
time.sleep(2)
def detect(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
written = self.serial.write(kiss_command)
@@ -442,9 +503,12 @@ class RNodeInterface(Interface):
raise IOError("An IO error occurred while configuring radio state for "+str(self))
def validate_firmware(self):
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
self.firmware_ok = True
if (self.maj_version > RNodeInterface.REQUIRED_FW_VER_MAJ):
self.firmware_ok = True
else:
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
self.firmware_ok = True
if self.firmware_ok:
return
@@ -457,7 +521,14 @@ class RNodeInterface(Interface):
def validateRadioState(self):
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
sleep(0.25);
if self.use_ble:
sleep(1.00)
else:
sleep(0.25)
if self.use_ble and self.ble != None and self.ble.device_disappeared:
RNS.log(f"Device disappeared during radio state validation for {self}", RNS.LOG_ERROR)
return False
self.validcfg = True
if (self.r_frequency != None and abs(self.frequency - int(self.r_frequency)) > 100):
@@ -617,7 +688,6 @@ class RNodeInterface(Interface):
self.r_state = byte
if self.r_state:
pass
#RNS.log(str(self)+" Radio reporting state is online", RNS.LOG_DEBUG)
else:
RNS.log(str(self)+" Radio reporting state is offline", RNS.LOG_DEBUG)
@@ -671,6 +741,18 @@ 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
@@ -749,6 +831,25 @@ class RNodeInterface(Interface):
RNS.log(str(self)+" Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms (at "+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
RNS.log(str(self)+" Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
RNS.log(str(self)+" Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
elif (command == KISS.CMD_STAT_BAT):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 2):
bat_percent = command_buffer[1]
if bat_percent > 100:
bat_percent = 100
if bat_percent < 0:
bat_percent = 0
self.r_battery_state = command_buffer[0]
self.r_battery_percent = bat_percent
elif (command == KISS.CMD_RANDOM):
self.r_random = byte
elif (command == KISS.CMD_PLATFORM):
@@ -762,6 +863,12 @@ class RNodeInterface(Interface):
elif (byte == KISS.ERROR_TXFAILED):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Hardware transmit failure")
elif (byte == KISS.ERROR_MEMORY_LOW):
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Memory exhausted", RNS.LOG_ERROR)
self.hw_errors.append({"error": KISS.ERROR_MEMORY_LOW, "description": "Memory exhausted on connected device"})
elif (byte == KISS.ERROR_MODEM_TIMEOUT):
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Modem communication timed out", RNS.LOG_ERROR)
self.hw_errors.append({"error": KISS.ERROR_MODEM_TIMEOUT, "description": "Modem communication timed out on connected device"})
else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Unknown hardware failure")
@@ -782,7 +889,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
@@ -836,9 +943,219 @@ class RNodeInterface(Interface):
self.disable_external_framebuffer()
self.setRadioState(KISS.RADIO_STATE_OFF)
self.leave()
if self.use_ble:
self.ble.close()
def should_ingress_limit(self):
return False
def get_battery_state(self):
return self.r_battery_state
def get_battery_state_string(self):
if self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGED:
return "charged"
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGING:
return "charging"
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_DISCHARGING:
return "discharging"
else:
return "unknown"
def get_battery_percent(self):
return self.r_battery_percent
def ble_receive(self, data):
with self.ble_rx_lock:
self.ble_rx_queue += data
def ble_waiting(self):
return len(self.ble_tx_queue) > 0
def get_ble_waiting(self, n):
with self.ble_tx_lock:
data = self.ble_tx_queue[:n]
self.ble_tx_queue = self.ble_tx_queue[n:]
return data
def __str__(self):
return "RNodeInterface["+str(self.name)+"]"
class BLEConnection():
UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
bleak = None
SCAN_TIMEOUT = 2.0
CONNECT_TIMEOUT = 5.0
@property
def is_open(self):
return self.connected
@property
def in_waiting(self):
buflen = len(self.owner.ble_rx_queue)
return buflen > 0
def write(self, data_bytes):
with self.owner.ble_tx_lock:
self.owner.ble_tx_queue += data_bytes
return len(data_bytes)
def read(self, n):
with self.owner.ble_rx_lock:
data = self.owner.ble_rx_queue[:n]
self.owner.ble_rx_queue = self.owner.ble_rx_queue[n:]
return data
def close(self):
if self.connected and self.ble_device:
RNS.log(f"Disconnecting BLE device from {self.owner}", RNS.LOG_DEBUG)
self.must_disconnect = True
while self.connect_job_running:
time.sleep(0.1)
def __init__(self, owner=None, target_name=None, target_bt_addr=None):
self.owner = owner
self.target_name = target_name
self.target_bt_addr = target_bt_addr
self.scan_timeout = BLEConnection.SCAN_TIMEOUT
self.ble_device = None
self.last_client = None
self.connected = False
self.running = False
self.should_run = False
self.must_disconnect = False
self.connect_job_running = False
self.device_disappeared = False
import importlib
if BLEConnection.bleak == None:
if importlib.util.find_spec("bleak") != None:
import bleak
BLEConnection.bleak = bleak
import asyncio
BLEConnection.asyncio = asyncio
else:
RNS.log("Using the RNode interface over BLE requires a the \"bleak\" module to be installed.", RNS.LOG_CRITICAL)
RNS.log("You can install one with the command: python3 -m pip install bleak", RNS.LOG_CRITICAL)
RNS.panic()
self.should_run = True
self.connection_thread = threading.Thread(target=self.connection_job, daemon=True).start()
def cleanup(self):
try:
if self.last_client != None:
self.asyncio.run(self.last_client.disconnect())
except Exception as e:
RNS.log(f"Error while disconnecting BLE device on cleanup for {self.owner}", RNS.LOG_ERROR)
self.should_run = False
def connection_job(self):
while self.should_run:
if self.ble_device == None:
self.ble_device = self.find_target_device()
if type(self.ble_device) == self.bleak.backends.device.BLEDevice:
if not self.connected:
self.connect_device()
time.sleep(1)
self.cleanup()
self.running = False
RNS.log(f"BLE connection job for {self.owner} ended", RNS.LOG_DEBUG)
def connect_device(self):
if self.ble_device != None and type(self.ble_device) == self.bleak.backends.device.BLEDevice:
RNS.log(f"Connecting BLE device {self.ble_device} for {self.owner}...", RNS.LOG_DEBUG)
async def connect_job():
self.connect_job_running = True
async with self.bleak.BleakClient(self.ble_device, disconnected_callback=self.device_disconnected) as ble_client:
def handle_rx(device, data):
if self.owner != None:
self.owner.ble_receive(data)
self.connected = True
self.ble_device = ble_client
self.last_client = ble_client
self.owner.port = str(f"ble://{ble_client.address}")
loop = self.asyncio.get_running_loop()
uart_service = ble_client.services.get_service(BLEConnection.UART_SERVICE_UUID)
rx_characteristic = uart_service.get_characteristic(BLEConnection.UART_RX_CHAR_UUID)
await ble_client.start_notify(BLEConnection.UART_TX_CHAR_UUID, handle_rx)
while self.connected:
if self.owner != None and self.owner.ble_waiting():
outbound_data = self.owner.get_ble_waiting(rx_characteristic.max_write_without_response_size)
await ble_client.write_gatt_char(rx_characteristic, outbound_data, response=False)
elif self.must_disconnect:
await ble_client.disconnect()
else:
await self.asyncio.sleep(0.1)
try:
self.asyncio.run(connect_job())
except Exception as e:
RNS.log(f"Could not connect BLE device {self.ble_device} for {self.owner}. Possibly missing authentication.", RNS.LOG_ERROR)
self.connect_job_running = False
def device_disconnected(self, device):
RNS.log(f"BLE device for {self.owner} disconnected", RNS.LOG_NOTICE)
self.connected = False
self.ble_device = None
self.device_disappeared = True
def find_target_device(self):
RNS.log(f"Searching for attachable BLE device for {self.owner}...", RNS.LOG_EXTREME)
def device_filter(device: self.bleak.backends.device.BLEDevice, adv: self.bleak.backends.scanner.AdvertisementData):
if BLEConnection.UART_SERVICE_UUID.lower() in adv.service_uuids:
if self.device_bonded(device):
if self.target_bt_addr == None and self.target_name == None:
if device.name.startswith("RNode "):
return True
if self.target_bt_addr == None or (device.address != None and device.address == self.target_bt_addr):
if self.target_name == None or (device.name != None and device.name == self.target_name):
return True
else:
if self.target_bt_addr != None and device.address == self.target_bt_addr:
RNS.log(f"Can't connect to target device {self.target_bt_addr} over BLE, device is not bonded", RNS.LOG_ERROR)
elif self.target_name != None and device.name == self.target_name:
RNS.log(f"Can't connect to target device {self.target_name} over BLE, device is not bonded", RNS.LOG_ERROR)
return False
device = None
try:
device = self.asyncio.run(self.bleak.BleakScanner.find_device_by_filter(device_filter, timeout=self.scan_timeout))
except Exception as e:
RNS.log(f"Error while finding BLE device for {self.owner}: {e}", RNS.LOG_ERROR)
self.should_run = False
return device
def device_bonded(self, device):
try:
if hasattr(device, "details"):
if "props" in device.details and "Bonded" in device.details["props"]:
if device.details["props"]["Bonded"] == True:
return True
except Exception as e:
RNS.log(f"Error while determining device bond status for {device}, the contained exception was: {e}", RNS.LOG_ERROR)
return False
File diff suppressed because it is too large Load Diff
+3
View File
@@ -116,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
@@ -200,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
+258 -133
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -68,6 +68,7 @@ class Link:
Timeout for link establishment in seconds per hop to destination.
"""
TRAFFIC_TIMEOUT_MIN_MS = 5
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_TIMEOUT_FACTOR = 4
"""
@@ -112,9 +113,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()
@@ -123,7 +125,7 @@ class Link:
link.last_inbound = time.time()
link.start_watchdog()
RNS.log("Incoming link request "+str(link)+" accepted", RNS.LOG_VERBOSE)
RNS.log("Incoming link request "+str(link)+" accepted on "+str(link.attached_interface), RNS.LOG_DEBUG)
return link
except Exception as e:
@@ -132,7 +134,7 @@ class Link:
return None
else:
RNS.log("Invalid link request payload size, dropping request", RNS.LOG_VERBOSE)
RNS.log("Invalid link request payload size, dropping request", RNS.LOG_DEBUG)
return None
@@ -141,6 +143,7 @@ class Link:
raise TypeError("Links can only be established to the \"single\" destination type")
self.rtt = None
self.establishment_cost = 0
self.establishment_rate = None
self.callbacks = LinkCallbacks()
self.resource_strategy = Link.ACCEPT_NONE
self.outgoing_resources = []
@@ -149,10 +152,14 @@ class Link:
self.last_inbound = 0
self.last_outbound = 0
self.last_proof = 0
self.last_data = 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
@@ -163,16 +170,21 @@ class Link:
self.type = RNS.Destination.LINK
self.owner = owner
self.destination = destination
self.expected_hops = None
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.expected_hops = RNS.Transport.hops_to(self.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()
@@ -208,6 +220,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):
@@ -345,7 +358,7 @@ class Link:
packed_request = umsgpack.packb(unpacked_request)
if timeout == None:
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME/4.0
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME*1.125
if len(packed_request) <= Link.MDU:
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
@@ -385,24 +398,57 @@ class Link:
try:
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
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)
try:
if self.owner.callbacks.link_established != None:
self.owner.callbacks.link_established(self)
except Exception as e:
RNS.log("Error occurred in external link establishment callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
except Exception as e:
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
self.teardown()
def 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.
@@ -418,9 +464,18 @@ class Link:
def get_context(self):
return None
def get_age(self):
"""
:returns: The time in seconds since this link was established.
"""
if self.activated_at:
return time.time() - self.activated_at
else:
return None
def no_inbound_for(self):
"""
:returns: The time in seconds since last inbound packet on the link.
:returns: The time in seconds since last inbound packet on the link. This includes keepalive packets.
"""
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(self.last_inbound, activated_at)
@@ -428,13 +483,19 @@ class Link:
def no_outbound_for(self):
"""
:returns: The time in seconds since last outbound packet on the link.
:returns: The time in seconds since last outbound packet on the link. This includes keepalive packets.
"""
return time.time() - self.last_outbound
def no_data_for(self):
"""
:returns: The time in seconds since payload data traversed the link. This excludes keepalive packets.
"""
return time.time() - self.last_data
def inactive_for(self):
"""
:returns: The time in seconds since activity on the link.
:returns: The time in seconds since activity on the link. This includes keepalive packets.
"""
return min(self.no_inbound_for(), self.no_outbound_for())
@@ -444,8 +505,10 @@ class Link:
"""
return self.__remote_identity
def had_outbound(self):
def had_outbound(self, is_keepalive=False):
self.last_outbound = time.time()
if not is_keepalive:
self.last_data = self.last_outbound
def teardown(self):
"""
@@ -472,6 +535,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
@@ -577,10 +641,25 @@ 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()
self.had_outbound()
self.had_outbound(is_keepalive = True)
def handle_request(self, request_id, unpacked_request):
if self.status == Link.ACTIVE:
@@ -631,7 +710,9 @@ class Link:
remove = pending_request
try:
pending_request.response_size = response_size
pending_request.response_transfer_size = response_transfer_size
if pending_request.response_transfer_size == None:
pending_request.response_transfer_size = 0
pending_request.response_transfer_size += response_transfer_size
pending_request.response_received(response_data)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -681,138 +762,176 @@ class Link:
self.watchdog_lock = True
if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])):
if packet.receiving_interface != self.attached_interface:
RNS.log("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
RNS.log(f"Link-associated packet received on unexpected interface {packet.receiving_interface} instead of {self.attached_interface}! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
else:
self.last_inbound = time.time()
if packet.context != RNS.Packet.KEEPALIVE:
self.last_data = self.last_inbound
self.rx += 1
self.rxbytes += len(packet.data)
if self.status == Link.STALE:
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()
packet.ratchet_id = self.link_id
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)
if response_resource != None:
if pending_request.response_size == None:
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
if pending_request.response_transfer_size == None:
pending_request.response_transfer_size = 0
pending_request.response_transfer_size += RNS.ResourceAdvertisement.read_transfer_size(packet)
if pending_request.started_at == None:
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)
# TODO: Test and possibly enable this at some point
# def request_job():
# resource.request(plaintext)
# threading.Thread(target=request_job, daemon=True).start()
elif packet.context == RNS.Packet.RESOURCE_HMU:
plaintext = self.decrypt(packet.data)
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]):
keepalive_packet = RNS.Packet(self, bytes([0xFE]), context=RNS.Packet.KEEPALIVE)
keepalive_packet.send()
self.had_outbound()
self.had_outbound(is_keepalive = True)
# TODO: find the most efficient way to allow multiple
@@ -822,27 +941,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:
# TODO: Remove packet loss simulator ######
# if not hasattr(self, "drop_counter"):
# self.drop_counter = 0
# self.drop_counter += 1
# if self.drop_counter%6 == 0:
# RNS.log("Dropping channel packet for testing", RNS.LOG_DEBUG)
# else:
# packet.prove()
# plaintext = self.decrypt(packet.data)
# self._channel._receive(plaintext)
############################################
packet.prove()
plaintext = self.decrypt(packet.data)
self._channel._receive(plaintext)
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:
@@ -850,6 +959,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
@@ -879,6 +989,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):
@@ -1059,7 +1170,8 @@ class RequestReceipt():
def request_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
RNS.log("Request "+RNS.prettyhexrep(self.request_id)+" successfully sent as resource.", RNS.LOG_DEBUG)
self.started_at = time.time()
if self.started_at == None:
self.started_at = time.time()
self.status = RequestReceipt.DELIVERED
self.__resource_response_timeout = time.time()+self.timeout
response_timeout_thread = threading.Thread(target=self.__response_timeout_job)
@@ -1100,24 +1212,26 @@ class RequestReceipt():
def response_resource_progress(self, resource):
if not self.status == RequestReceipt.FAILED:
self.status = RequestReceipt.RECEIVING
if self.packet_receipt != None:
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
self.packet_receipt.proved = True
self.packet_receipt.concluded_at = time.time()
if self.packet_receipt.callbacks.delivery != None:
self.packet_receipt.callbacks.delivery(self.packet_receipt)
if resource != None:
if not self.status == RequestReceipt.FAILED:
self.status = RequestReceipt.RECEIVING
if self.packet_receipt != None:
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()
if self.callbacks.progress != None:
try:
self.callbacks.progress(self)
except Exception as e:
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
resource.cancel()
self.progress = resource.get_progress()
if self.callbacks.progress != None:
try:
self.callbacks.progress(self)
except Exception as e:
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
resource.cancel()
def response_received(self, response):
@@ -1182,6 +1296,17 @@ class RequestReceipt():
else:
return None
def concluded(self):
"""
:returns: True if the associated request has concluded (successfully or with a failure), otherwise False.
"""
if self.status == RequestReceipt.READY:
return True
elif self.status == RequestReceipt.FAILED:
return True
else:
return False
class RequestReceiptCallbacks:
+23 -9
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -83,6 +83,10 @@ class Packet:
LRRTT = 0xFE # Packet is a link request round-trip time measurement
LRPROOF = 0xFF # Packet is a link request proof
# Context flag values
FLAG_SET = 0x01
FLAG_UNSET = 0x00
# This is used to calculate allowable
# payload sizes
HEADER_MAXSIZE = RNS.Reticulum.HEADER_MAXSIZE
@@ -102,7 +106,9 @@ class Packet:
TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST,
header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True, context_flag=FLAG_UNSET):
if destination != None:
if transport_type == None:
transport_type = RNS.Transport.BROADCAST
@@ -111,6 +117,7 @@ class Packet:
self.packet_type = packet_type
self.transport_type = transport_type
self.context = context
self.context_flag = context_flag
self.hops = 0;
self.destination = destination
@@ -133,17 +140,20 @@ class Packet:
self.MTU = RNS.Reticulum.MTU
self.sent_at = None
self.packet_hash = None
self.ratchet_id = None
self.attached_interface = attached_interface
self.receiving_interface = None
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 << 2) | self.packet_type
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
else:
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
return packed_flags
def pack(self):
@@ -186,6 +196,8 @@ class Packet:
# In all other cases, we encrypt the packet
# with the destination's encryption method
self.ciphertext = self.destination.encrypt(self.data)
if hasattr(self.destination, "latest_ratchet_id"):
self.ratchet_id = self.destination.latest_ratchet_id
if self.header_type == Packet.HEADER_2:
if self.transport_id != None:
@@ -215,7 +227,8 @@ class Packet:
self.hops = self.raw[1]
self.header_type = (self.flags & 0b01000000) >> 6
self.transport_type = (self.flags & 0b00110000) >> 4
self.context_flag = (self.flags & 0b00100000) >> 5
self.transport_type = (self.flags & 0b00010000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)
@@ -368,10 +381,10 @@ class PacketReceipt:
self.proof_packet = None
if packet.destination.type == RNS.Destination.LINK:
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
self.timeout = max(packet.destination.rtt * packet.destination.traffic_timeout_factor, RNS.Link.TRAFFIC_TIMEOUT_MIN_MS/1000)
else:
self.timeout = 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):
"""
@@ -408,6 +421,7 @@ class PacketReceipt:
except Exception as e:
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
return True
else:
@@ -439,7 +453,7 @@ class PacketReceipt:
# This is an explicit proof
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
signature = proof[RNS.Identity.HASHLENGTH//8:RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8]
if proof_hash == self.hash:
if proof_hash == self.hash and hasattr(self.destination, "identity") and self.destination.identity != None:
proof_valid = self.destination.identity.validate(signature, self.hash)
if proof_valid:
self.status = PacketReceipt.DELIVERED
+234 -111
View File
@@ -25,7 +25,9 @@ import os
import bz2
import math
import time
import tempfile
import threading
from threading import Lock
from .vendor import umsgpack as umsgpack
from time import sleep
@@ -47,11 +49,14 @@ 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
# The maximum window size for transfers on very slow links
WINDOW_MAX_VERY_SLOW = 4
# The maximum window size for transfers on fast links
WINDOW_MAX_FAST = 75
@@ -63,12 +68,22 @@ class Resource:
# rounds, the fast link window size will be allowed.
FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2
# If the very slow rate is sustained for this many request
# rounds, window will be capped to the very slow limit.
VERY_SLOW_RATE_THRESHOLD = 2
# If the RTT rate is higher than this value,
# the max window size for fast links will be used.
# The default is 50 Kbps (the value is stored in
# bytes per second, hence the "/ 8").
RATE_FAST = (50*1000) / 8
# If the RTT rate is lower than this value,
# the window size will be capped at .
# The default is 50 Kbps (the value is stored in
# bytes per second, hence the "/ 8").
RATE_VERY_SLOW = (2*1000) / 8
# The minimum allowed flexibility of the window size.
# The difference between window_max and window_min
# will never be smaller than this value.
@@ -103,9 +118,11 @@ class Resource:
PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
MAX_RETRIES = 8
PROOF_TIMEOUT_FACTOR = 3
MAX_RETRIES = 16
MAX_ADV_RETRIES = 4
SENDER_GRACE_TIME = 10
SENDER_GRACE_TIME = 10.0
PROCESSING_GRACE = 1.0
RETRY_GRACE_TIME = 0.25
PER_RETRY_DELAY = 0.5
@@ -144,7 +161,7 @@ class Resource:
resource.encrypted = True if resource.flags & 0x01 else False
resource.compressed = True if resource.flags >> 1 & 0x01 else False
resource.initiator = False
resource.callback = callback
resource.callback = callback
resource.__progress_callback = progress_callback
resource.total_parts = int(math.ceil(resource.size/float(Resource.SDU)))
resource.received_count = 0
@@ -167,15 +184,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)
@@ -203,11 +218,22 @@ class Resource:
data_size = None
resource_data = None
self.assembly_lock = False
self.preparing_next_segment = False
self.next_segment = None
if data != None:
if not hasattr(data, "read") and len(data) > Resource.MAX_EFFICIENT_SIZE:
original_data = data
data_size = len(original_data)
data = tempfile.TemporaryFile()
data.write(original_data)
del original_data
if hasattr(data, "read"):
data_size = os.stat(data.name).st_size
self.total_size = data_size
self.grand_total_parts = math.ceil(data_size/Resource.SDU)
if data_size == None:
data_size = os.stat(data.name).st_size
self.total_size = data_size
if data_size <= Resource.MAX_EFFICIENT_SIZE:
self.total_segments = 1
@@ -228,7 +254,6 @@ class Resource:
elif isinstance(data, bytes):
data_size = len(data)
self.grand_total_parts = math.ceil(data_size/Resource.SDU)
self.total_size = data_size
resource_data = data
@@ -262,6 +287,7 @@ class Resource:
self.req_resp_rtt_rate = 0
self.rtt_rxd_bytes_at_part_req = 0
self.fast_rate_rounds = 0
self.very_slow_rate_rounds = 0
self.request_id = request_id
self.is_response = is_response
@@ -279,7 +305,7 @@ class Resource:
self.uncompressed_data = data
compression_began = time.time()
if (auto_compress and len(self.uncompressed_data) < Resource.AUTO_COMPRESS_MAX_SIZE):
if (auto_compress and len(self.uncompressed_data) <= Resource.AUTO_COMPRESS_MAX_SIZE):
RNS.log("Compressing resource data...", RNS.LOG_DEBUG)
self.compressed_data = bz2.compress(self.uncompressed_data)
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_DEBUG)
@@ -321,6 +347,7 @@ class Resource:
self.size = len(self.data)
self.sent_parts = 0
hashmap_entries = int(math.ceil(self.size/float(Resource.SDU)))
self.total_parts = hashmap_entries
hashmap_ok = False
while not hashmap_ok:
@@ -366,7 +393,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:
@@ -398,10 +426,13 @@ class Resource:
Advertise the resource. If the other end of the link accepts
the resource advertisement it will begin transferring.
"""
thread = threading.Thread(target=self.__advertise_job)
thread.daemon = True
thread = threading.Thread(target=self.__advertise_job, daemon=True)
thread.start()
if self.segment_index < self.total_segments:
prepare_thread = threading.Thread(target=self.__prepare_next_segment, daemon=True)
prepare_thread.start()
def __advertise_job(self):
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
while not self.link.ready_for_new_resource():
@@ -440,7 +471,7 @@ class Resource:
sleep_time = None
if self.status == Resource.ADVERTISED:
sleep_time = (self.adv_sent+self.timeout)-time.time()
sleep_time = (self.adv_sent+self.timeout+Resource.PROCESSING_GRACE)-time.time()
if sleep_time < 0:
if self.retries_left <= 0:
RNS.log("Resource transfer timeout after sending advertisement", RNS.LOG_DEBUG)
@@ -456,7 +487,7 @@ class Resource:
self.adv_sent = self.last_activity
sleep_time = 0.001
except Exception as e:
RNS.log("Could not resend advertisement packet, cancelling resource", RNS.LOG_VERBOSE)
RNS.log("Could not resend advertisement packet, cancelling resource. The contained exception was: "+str(e), RNS.LOG_VERBOSE)
self.cancel()
@@ -502,6 +533,10 @@ class Resource:
sleep_time = 0.001
elif self.status == Resource.AWAITING_PROOF:
# Decrease timeout factor since proof packets are
# significantly smaller than full req/resp roundtrip
self.timeout_factor = Resource.PROOF_TIMEOUT_FACTOR
sleep_time = self.last_part_sent + (self.rtt*self.timeout_factor+self.sender_grace_time) - time.time()
if sleep_time < 0:
if self.retries_left <= 0:
@@ -593,11 +628,27 @@ class Resource:
proof_data = self.hash+proof
proof_packet = RNS.Packet(self.link, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
proof_packet.send()
RNS.Transport.cache(proof_packet, force_cache=True)
except Exception as e:
RNS.log("Could not send proof packet, cancelling resource", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
self.cancel()
def __prepare_next_segment(self):
# Prepare the next segment for advertisement
RNS.log(f"Preparing segment {self.segment_index+1} of {self.total_segments} for resource {self}", RNS.LOG_DEBUG)
self.preparing_next_segment = True
self.next_segment = Resource(
self.input_file, self.link,
callback = self.callback,
segment_index = self.segment_index+1,
original_hash=self.original_hash,
progress_callback = self.__progress_callback,
request_id = self.request_id,
is_response = self.is_response,
advertise = False,
)
def validate_proof(self, proof_data):
if not self.status == Resource.FAILED:
if len(proof_data) == RNS.Identity.HASHLENGTH//8*2:
@@ -612,10 +663,24 @@ class Resource:
self.callback(self)
except Exception as e:
RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
finally:
try:
if hasattr(self, "input_file"):
if hasattr(self.input_file, "close") and callable(self.input_file.close):
self.input_file.close()
except Exception as e:
RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
else:
# Otherwise we'll recursively create the
# next segment of the resource
Resource(self.input_file, self.link, callback = self.callback, segment_index = self.segment_index+1, original_hash=self.original_hash, progress_callback = self.__progress_callback)
if not self.preparing_next_segment:
RNS.log(f"Next segment preparation for resource {self} was not started yet, manually preparing now. This will cause transfer slowdown.", RNS.LOG_WARNING)
self.__prepare_next_segment()
while self.next_segment == None:
time.sleep(0.05)
self.next_segment.advertise()
else:
pass
else:
@@ -623,99 +688,105 @@ 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:
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:
# 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
# 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
# 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
# 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
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)
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)
i += 1
i += 1
self.receiving_part = False
self.receiving_part = False
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 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 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_sent != 0:
rtt = time.time()-self.req_sent
req_transferred = self.rtt_rxd_bytes - self.rtt_rxd_bytes_at_part_req
if rtt != 0:
self.req_data_rtt_rate = req_transferred/rtt
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
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_data_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
self.fast_rate_rounds += 1
if self.req_data_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
self.request_next()
else:
self.receiving_part = False
if self.fast_rate_rounds == 0 and self.req_data_rtt_rate < Resource.RATE_VERY_SLOW and self.very_slow_rate_rounds < Resource.VERY_SLOW_RATE_THRESHOLD:
self.very_slow_rate_rounds += 1
if self.very_slow_rate_rounds == Resource.VERY_SLOW_RATE_THRESHOLD:
self.window_max = Resource.WINDOW_MAX_VERY_SLOW
self.request_next()
else:
self.receiving_part = False
# Called on incoming resource to send a request for more data
def request_next(self):
@@ -728,11 +799,11 @@ 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
for part in self.parts[search_start:search_start+self.window]:
search_size = self.window
for part in self.parts[search_start:search_start+search_size]:
if part == None:
part_hash = self.hashmap[pn]
if part_hash != None:
@@ -752,7 +823,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)
@@ -855,6 +925,7 @@ class Resource:
if self.sent_parts == len(self.parts):
self.status = Resource.AWAITING_PROOF
self.retries_left = 3
if self.__progress_callback != None:
try:
@@ -896,20 +967,68 @@ class Resource:
"""
:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
"""
if self.initiator:
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
self.processed_parts += self.sent_parts
self.progress_total_parts = float(self.grand_total_parts)
else:
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
self.processed_parts += self.received_count
if self.split:
self.progress_total_parts = float(math.ceil(self.total_size/Resource.SDU))
else:
if self.status == RNS.Resource.COMPLETE and self.segment_index == self.total_segments:
return 1.0
elif self.initiator:
if not self.split:
self.processed_parts = self.sent_parts
self.progress_total_parts = float(self.total_parts)
progress = self.processed_parts / self.progress_total_parts
else:
is_last_segment = self.segment_index != self.total_segments
total_segments = self.total_segments
processed_segments = self.segment_index-1
current_segment_parts = self.total_parts
max_parts_per_segment = math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
previously_processed_parts = processed_segments*max_parts_per_segment
if current_segment_parts < max_parts_per_segment:
current_segment_factor = max_parts_per_segment / current_segment_parts
else:
current_segment_factor = 1
self.processed_parts = previously_processed_parts + self.sent_parts*current_segment_factor
self.progress_total_parts = self.total_segments*max_parts_per_segment
else:
if not self.split:
self.processed_parts = self.received_count
self.progress_total_parts = float(self.total_parts)
else:
is_last_segment = self.segment_index != self.total_segments
total_segments = self.total_segments
processed_segments = self.segment_index-1
current_segment_parts = self.total_parts
max_parts_per_segment = math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
previously_processed_parts = processed_segments*max_parts_per_segment
if current_segment_parts < max_parts_per_segment:
current_segment_factor = max_parts_per_segment / current_segment_parts
else:
current_segment_factor = 1
self.processed_parts = previously_processed_parts + self.received_count*current_segment_factor
self.progress_total_parts = self.total_segments*max_parts_per_segment
progress = min(1.0, self.processed_parts / self.progress_total_parts)
return progress
def get_segment_progress(self):
if self.status == RNS.Resource.COMPLETE and self.segment_index == self.total_segments:
return 1.0
elif self.initiator:
processed_parts = self.sent_parts
else:
processed_parts = self.received_count
progress = min(1.0, processed_parts / self.total_parts)
return progress
def get_transfer_size(self):
@@ -997,6 +1116,7 @@ class ResourceAdvertisement:
def __init__(self, resource=None, request_id=None, is_response=False):
self.link = None
if resource != None:
self.t = resource.size # Transfer size
self.d = resource.total_size # Total uncompressed data size
@@ -1043,6 +1163,9 @@ class ResourceAdvertisement:
def is_compressed(self):
return self.c
def get_link(self):
return self.link
def pack(self, segment=0):
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
hashmap_end = min((segment+1)*(ResourceAdvertisement.HASHMAP_MAX_LEN), self.n)
+309 -59
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,6 +29,7 @@ if get_platform() == "android":
from .Interfaces import TCPInterface
from .Interfaces import UDPInterface
from .Interfaces import I2PInterface
from .Interfaces import RNodeMultiInterface
from .Interfaces.Android import RNodeInterface
from .Interfaces.Android import SerialInterface
from .Interfaces.Android import KISSInterface
@@ -79,7 +80,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,17 +107,19 @@ 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.
# 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.
@@ -145,6 +148,8 @@ class Reticulum:
configpath = ""
storagepath = ""
cachepath = ""
__instance = None
@staticmethod
def exit_handler():
@@ -156,6 +161,9 @@ class Reticulum:
RNS.Transport.exit_handler()
RNS.Identity.exit_handler()
if RNS.profiler_ran:
RNS.profiler_results()
@staticmethod
def sigint_handler(signal, frame):
RNS.Transport.detach_interfaces()
@@ -168,6 +176,13 @@ class Reticulum:
RNS.exit()
@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
@@ -177,6 +192,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 +220,7 @@ class Reticulum:
Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
Reticulum.__transport_enabled = False
Reticulum.__remote_management_enabled = False
Reticulum.__use_implicit_proof = True
Reticulum.__allow_probes = False
@@ -278,6 +299,7 @@ class Reticulum:
def __start_jobs(self):
if self.jobs_thread == None:
RNS.Identity._clean_ratchets()
self.jobs_thread = threading.Thread(target=self.__jobs)
self.jobs_thread.daemon = True
self.jobs_thread.start()
@@ -303,6 +325,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
@@ -317,11 +343,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.__remote_management_enabled = False
Reticulum.__allow_probes = False
RNS.log("Connected to locally available Reticulum instance via: "+str(interface), RNS.LOG_DEBUG)
except Exception as e:
@@ -372,10 +403,30 @@ class Reticulum:
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.__transport_enabled = True
if option == "enable_remote_management":
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.__remote_management_enabled = True
if option == "remote_management_allowed":
v = self.config["reticulum"].as_list(option)
for hexhash in v:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(hexhash) != dest_len:
raise ValueError("Identity hash length for remote management ACL "+str(hexhash)+" is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
allowed_hash = bytes.fromhex(hexhash)
except Exception as e:
raise ValueError("Invalid identity hash for remote management ACL: "+str(hexhash))
if not allowed_hash in RNS.Transport.remote_management_allowed:
RNS.Transport.remote_management_allowed.append(allowed_hash)
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:
@@ -509,44 +560,40 @@ class Reticulum:
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
if c["type"] == "AutoInterface":
if not RNS.vendor.platformutils.is_windows():
group_id = c["group_id"] if "group_id" in c else None
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
data_port = int(c["data_port"]) if "data_port" in c else None
allowed_interfaces = c.as_list("devices") if "devices" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
group_id = c["group_id"] if "group_id" in c else None
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
multicast_address_type = c["multicast_address_type"] if "multicast_address_type" in c else None
data_port = int(c["data_port"]) if "data_port" in c else None
allowed_interfaces = c.as_list("devices") if "devices" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
interface = AutoInterface.AutoInterface(
RNS.Transport,
name,
group_id,
discovery_scope,
discovery_port,
data_port,
allowed_interfaces,
ignored_interfaces
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
interface = AutoInterface.AutoInterface(
RNS.Transport,
name,
group_id,
discovery_scope,
discovery_port,
multicast_address_type,
data_port,
allowed_interfaces,
ignored_interfaces
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
RNS.log("AutoInterface is not currently supported on Windows, disabling interface.", RNS.LOG_ERROR);
RNS.log("Please remove this AutoInterface instance from your configuration file.", RNS.LOG_ERROR);
RNS.log("You will have to manually configure other interfaces for connectivity.", RNS.LOG_ERROR);
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
if c["type"] == "UDPInterface":
device = c["device"] if "device" in c else None
@@ -870,11 +917,28 @@ class Reticulum:
st_alock = float(c["airtime_limit_short"]) if "airtime_limit_short" in c else None
lt_alock = float(c["airtime_limit_long"]) if "airtime_limit_long" in c else None
force_ble = False
ble_name = None
ble_addr = None
port = c["port"] if "port" in c else None
if port == None:
raise ValueError("No port specified for RNode interface")
if port != None:
ble_uri_scheme = "ble://"
if port.lower().startswith(ble_uri_scheme):
force_ble = True
ble_string = port[len(ble_uri_scheme):]
port = None
if len(ble_string) == 0:
pass
elif len(ble_string.split(":")) == 6 and len(ble_string) == 17:
ble_addr = ble_string
else:
ble_name = ble_string
interface = RNodeInterface.RNodeInterface(
RNS.Transport,
name,
@@ -888,7 +952,10 @@ class Reticulum:
id_interval = id_interval,
id_callsign = id_callsign,
st_alock = st_alock,
lt_alock = lt_alock
lt_alock = lt_alock,
ble_addr = ble_addr,
ble_name = ble_name,
force_ble = force_ble,
)
if "outgoing" in c and c.as_bool("outgoing") == False:
@@ -906,6 +973,98 @@ class Reticulum:
else:
interface.ifac_size = 8
if c["type"] == "RNodeMultiInterface":
count = 0
enabled_count = 0
# Count how many interfaces are in the file
for subinterface in c:
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance(c[subinterface], str):
count += 1
# Count how many interfaces are enabled to allow for appropriate matrix sizing
for subinterface in c:
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance(c[subinterface], str):
subinterface_config = self.config["interfaces"][name][subinterface]
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
enabled_count += 1
# Create an array with a row for each subinterface
subint_config = [[0 for x in range(11)] for y in range(enabled_count)]
subint_index = 0
for subinterface in c:
# If the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance(c[subinterface], str):
subinterface_config = self.config["interfaces"][name][subinterface]
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
subint_config[subint_index][0] = subinterface
subint_vport = subinterface_config["vport"] if "vport" in subinterface_config else None
subint_config[subint_index][1] = subint_vport
frequency = int(subinterface_config["frequency"]) if "frequency" in subinterface_config else None
subint_config[subint_index][2] = frequency
bandwidth = int(subinterface_config["bandwidth"]) if "bandwidth" in subinterface_config else None
subint_config[subint_index][3] = bandwidth
txpower = int(subinterface_config["txpower"]) if "txpower" in subinterface_config else None
subint_config[subint_index][4] = txpower
spreadingfactor = int(subinterface_config["spreadingfactor"]) if "spreadingfactor" in subinterface_config else None
subint_config[subint_index][5] = spreadingfactor
codingrate = int(subinterface_config["codingrate"]) if "codingrate" in subinterface_config else None
subint_config[subint_index][6] = codingrate
flow_control = subinterface_config.as_bool("flow_control") if "flow_control" in subinterface_config else False
subint_config[subint_index][7] = flow_control
st_alock = float(subinterface_config["airtime_limit_short"]) if "airtime_limit_short" in subinterface_config else None
subint_config[subint_index][8] = st_alock
lt_alock = float(subinterface_config["airtime_limit_long"]) if "airtime_limit_long" in subinterface_config else None
subint_config[subint_index][9] = lt_alock
if "outgoing" in subinterface_config and subinterface_config.as_bool("outgoing") == False:
subint_config[subint_index][10] = False
else:
subint_config[subint_index][10] = True
subint_index += 1
# if no subinterfaces are defined
if count == 0:
raise ValueError("No subinterfaces configured for "+name)
# if no subinterfaces are enabled
elif enabled_count == 0:
raise ValueError("No subinterfaces enabled for "+name)
id_interval = int(c["id_interval"]) if "id_interval" in c else None
id_callsign = c["id_callsign"] if "id_callsign" in c else None
port = c["port"] if "port" in c else None
if port == None:
raise ValueError("No port specified for "+name)
interface = RNodeMultiInterface.RNodeMultiInterface(
RNS.Transport,
name,
port,
subint_config,
id_interval = id_interval,
id_callsign = id_callsign
)
interface.IN = False
interface.OUT = False
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
if interface != None:
interface.announce_rate_target = announce_rate_target
interface.announce_rate_grace = announce_rate_grace
@@ -944,6 +1103,9 @@ class Reticulum:
RNS.Transport.interfaces.append(interface)
if isinstance(interface, RNS.Interfaces.RNodeMultiInterface.RNodeMultiInterface):
interface.start()
else:
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
@@ -1063,7 +1225,8 @@ class Reticulum:
rpc_connection.send(self.get_interface_stats())
if path == "path_table":
rpc_connection.send(self.get_path_table())
mh = call["max_hops"]
rpc_connection.send(self.get_path_table(max_hops=mh))
if path == "rate_table":
rpc_connection.send(self.get_rate_table())
@@ -1074,12 +1237,21 @@ 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 == "link_count":
rpc_connection.send(self.get_link_count())
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"]
@@ -1113,6 +1285,10 @@ class Reticulum:
else:
ifstats["clients"] = None
if hasattr(interface, "parent_interface") and interface.parent_interface != None:
ifstats["parent_interface_name"] = str(interface.parent_interface)
ifstats["parent_interface_hash"] = interface.parent_interface.get_hash()
if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
if interface.connectable:
ifstats["i2p_connectable"] = True
@@ -1150,6 +1326,13 @@ class Reticulum:
if hasattr(interface, "r_channel_load_long"):
ifstats["channel_load_long"] = interface.r_channel_load_long
if hasattr(interface, "r_battery_state"):
if interface.r_battery_state != 0x00:
ifstats["battery_state"] = interface.get_battery_state_string()
if hasattr(interface, "r_battery_percent"):
ifstats["battery_percent"] = interface.r_battery_percent
if hasattr(interface, "bitrate"):
if interface.bitrate != None:
ifstats["bitrate"] = interface.bitrate
@@ -1178,6 +1361,9 @@ class Reticulum:
ifstats["announce_queue"] = None
ifstats["name"] = str(interface)
ifstats["short_name"] = str(interface.name)
ifstats["hash"] = interface.get_hash()
ifstats["type"] = str(type(interface).__name__)
ifstats["rxb"] = interface.rxb
ifstats["txb"] = interface.txb
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
@@ -1200,25 +1386,27 @@ class Reticulum:
return stats
def get_path_table(self):
def get_path_table(self, max_hops=None):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "path_table"})
rpc_connection.send({"get": "path_table", "max_hops": max_hops})
response = rpc_connection.recv()
return response
else:
path_table = []
for dst_hash in RNS.Transport.destination_table:
entry = {
"hash": dst_hash,
"timestamp": RNS.Transport.destination_table[dst_hash][0],
"via": RNS.Transport.destination_table[dst_hash][1],
"hops": RNS.Transport.destination_table[dst_hash][2],
"expires": RNS.Transport.destination_table[dst_hash][3],
"interface": str(RNS.Transport.destination_table[dst_hash][5]),
}
path_table.append(entry)
path_hops = RNS.Transport.destination_table[dst_hash][2]
if max_hops == None or path_hops <= max_hops:
entry = {
"hash": dst_hash,
"timestamp": RNS.Transport.destination_table[dst_hash][0],
"via": RNS.Transport.destination_table[dst_hash][1],
"hops": path_hops,
"expires": RNS.Transport.destination_table[dst_hash][3],
"interface": str(RNS.Transport.destination_table[dst_hash][5]),
}
path_table.append(entry)
return path_table
@@ -1289,16 +1477,51 @@ 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)
rpc_connection.send({"get": "next_hop", "destination_hash": destination})
response = rpc_connection.recv()
# TODO: Remove this debugging function
# if not response:
# response = RNS.Transport.next_hop(destination)
return response
else:
return RNS.Transport.next_hop(destination)
def get_link_count(self):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "link_count"})
response = rpc_connection.recv()
return response
else:
return len(RNS.Transport.link_table)
def get_packet_rssi(self, packet_hash):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
@@ -1327,6 +1550,20 @@ 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():
@@ -1351,6 +1588,19 @@ class Reticulum:
"""
return Reticulum.__transport_enabled
@staticmethod
def remote_management_enabled():
"""
Returns whether remote management is enabled for the
running instance.
When remote management is enabled, authenticated peers
can remotely query and manage this instance.
:returns: True if remote management is enabled, False if not.
"""
return Reticulum.__remote_management_enabled
@staticmethod
def probe_destination_enabled():
return Reticulum.__allow_probes
+505 -166
View File
File diff suppressed because it is too large Load Diff
+199 -63
View File
@@ -33,11 +33,23 @@ from RNS._version import __version__
APP_NAME = "rncp"
allow_all = False
allow_fetch = False
fetch_jail = None
save_path = None
show_phy_rates = False
allowed_identity_hashes = []
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None, announce = False):
global allow_all, allowed_identity_hashes
REQ_FETCH_NOT_ALLOWED = 0xF0
es = " "
erase_str = "\33[2K\r"
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False,
limit = None, disable_auth = None, fetch_allowed = False, jail = None, save = None, announce = False):
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path
from tempfile import TemporaryFile
allow_fetch = fetch_allowed
identity = None
if announce < 0:
announce = False
@@ -45,6 +57,24 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
if jail != None:
fetch_jail = os.path.abspath(os.path.expanduser(jail))
RNS.log("Restricting fetch requests to paths under \""+fetch_jail+"\"", RNS.LOG_VERBOSE)
if save != None:
sp = os.path.abspath(os.path.expanduser(save))
if os.path.isdir(sp):
if os.access(sp, os.W_OK):
save_path = sp
else:
RNS.log("Output directory not writable", RNS.LOG_ERROR)
exit(4)
else:
RNS.log("Output directory not found", RNS.LOG_ERROR)
exit(3)
RNS.log("Saving received files in \""+save_path+"\"", RNS.LOG_VERBOSE)
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
@@ -115,12 +145,25 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
print("Warning: No allowed identities configured, rncp will not accept any files!")
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
global allow_fetch, fetch_jail
if not allow_fetch:
return REQ_FETCH_NOT_ALLOWED
if fetch_jail:
if data.startswith(fetch_jail+"/"):
data = data.replace(fetch_jail+"/", "")
file_path = os.path.abspath(os.path.expanduser(f"{fetch_jail}/{data}"))
if not file_path.startswith(fetch_jail+"/"):
RNS.log(f"Disallowing fetch request for {file_path} outside of fetch jail {fetch_jail}", RNS.LOG_WARNING)
return REQ_FETCH_NOT_ALLOWED
else:
file_path = os.path.abspath(os.path.expanduser(f"{data}"))
target_link = None
for link in RNS.Transport.active_links:
if link.link_id == link_id:
target_link = link
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
@@ -136,8 +179,6 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
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)
@@ -151,7 +192,13 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
destination.set_link_established_callback(client_link_established)
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
if allow_fetch:
if allow_all:
RNS.log("Allowing unauthenticated fetch requests", RNS.LOG_WARNING)
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_ALL)
else:
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
if announce >= 0:
@@ -210,6 +257,7 @@ def receive_resource_started(resource):
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
def receive_resource_concluded(resource):
global save_path
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
@@ -218,12 +266,20 @@ def receive_resource_concluded(resource):
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
if not saved_filename.startswith(save_path+"/"):
RNS.log(f"Invalid save path {saved_filename}, ignoring", RNS.LOG_ERROR)
return
else:
saved_filename = filename
full_save_path = saved_filename
while os.path.isfile(full_save_path):
counter += 1
saved_filename = filename+"."+str(counter)
full_save_path = saved_filename+"."+str(counter)
file = open(saved_filename, "wb")
file = open(full_save_path, "wb")
file.write(resource.data.read())
file.close()
@@ -237,33 +293,57 @@ resource_done = False
current_resource = None
stats = []
speed = 0.0
phy_speed = 0.0
def sender_progress(resource):
stats_max = 32
global current_resource, stats, speed, resource_done
global current_resource, stats, speed, phy_speed, resource_done
current_resource = resource
now = time.time()
got = current_resource.get_progress()*current_resource.total_size
entry = [now, got]
got = current_resource.get_progress()*current_resource.get_data_size()
phy_got = current_resource.get_segment_progress()*current_resource.get_transfer_size()
entry = [now, got, phy_got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
phy_speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
phy_diff = phy_got - stats[0][2]
if phy_diff > 0:
phy_speed = phy_diff/span
if resource.status < RNS.Resource.COMPLETE:
resource_done = False
else:
resource_done = True
link = None
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
global current_resource, resource_done, link, speed
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None):
global current_resource, resource_done, link, speed, show_phy_rates, save_path
targetloglevel = 3+verbosity-quietness
show_phy_rates = phy_rates
if save:
sp = os.path.abspath(os.path.expanduser(save))
if os.path.isdir(sp):
if os.access(sp, os.W_OK):
save_path = sp
else:
RNS.log("Output directory not writable", RNS.LOG_ERROR)
exit(4)
else:
RNS.log("Output directory not found", RNS.LOG_ERROR)
exit(3)
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
@@ -298,7 +378,7 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
else:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=es)
sys.stdout.flush()
i = 0
@@ -315,13 +395,13 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("Path not found")
else:
print("\r \rPath not found")
print(f"{erase_str}Path not found")
exit(1)
else:
if silent:
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
print(f"{erase_str}Establishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=es)
listener_identity = RNS.Identity.recall(destination_hash)
listener_destination = RNS.Destination(
@@ -344,13 +424,13 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
print(f"{erase_str}Could not establish link with "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
if silent:
print("Requesting file from remote...")
else:
print("\r \rRequesting file from remote ", end=" ")
print(f"{erase_str}Requesting file from remote ", end=es)
link.identify(identity)
@@ -365,6 +445,8 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
request_status = "not_found"
elif request_receipt.response == None:
request_status = "remote_error"
elif request_receipt.response == REQ_FETCH_NOT_ALLOWED:
request_status = "fetch_not_allowed"
else:
request_status = "found"
@@ -383,18 +465,25 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
def fetch_resource_concluded(resource):
nonlocal resource_resolved, resource_status
global save_path
if resource.status == RNS.Resource.COMPLETE:
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
else:
saved_filename = filename
full_save_path = saved_filename
while os.path.isfile(full_save_path):
counter += 1
saved_filename = filename+"."+str(counter)
file = open(saved_filename, "wb")
full_save_path = saved_filename+"."+str(counter)
file = open(full_save_path, "wb")
file.write(resource.data.read())
file.close()
resource_status = "completed"
@@ -422,26 +511,32 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
sys.stdout.flush()
i = (i+1)%len(syms)
if request_status == "not_found":
if not silent: print("\r \r", end="")
if request_status == "fetch_not_allowed":
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed, fetching the file "+str(file)+" was not allowed by the remote")
link.teardown()
time.sleep(0.15)
exit(0)
elif request_status == "not_found":
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed, the file "+str(file)+" was not found on the remote")
link.teardown()
time.sleep(1)
time.sleep(0.15)
exit(0)
elif request_status == "remote_error":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed due to an error on the remote system")
link.teardown()
time.sleep(1)
time.sleep(0.15)
exit(0)
elif request_status == "unknown":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed due to an unknown error (probably not authorised)")
link.teardown()
time.sleep(1)
time.sleep(0.15)
exit(0)
elif request_status == "found":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
while not resource_resolved:
if not silent:
@@ -449,10 +544,21 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if current_resource:
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
if show_phy_rates:
pss = size_str(phy_speed, "b")
phy_str = f" ({pss}ps at physical layer)"
else:
phy_str = ""
ps = size_str(int(prg*current_resource.total_size))
ts = size_str(current_resource.total_size)
ss = size_str(speed, "b")
stat_str = f"{percent}% - {ps} of {ts} - {ss}ps{phy_str}"
if prg != 1.0:
print(f"{erase_str}Transferring file {syms[i]} {stat_str}", end=es)
else:
print(f"{erase_str}Transfer complete {stat_str}", end=es)
else:
print("\r \rWaiting for transfer to start "+syms[i]+" ", end=" ")
print(f"{erase_str}Waiting for transfer to start {syms[i]} ", end=es)
sys.stdout.flush()
i = (i+1)%len(syms)
@@ -460,25 +566,26 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("The transfer failed")
else:
print("\r \rThe transfer failed")
print(f"{erase_str}The transfer failed")
exit(1)
else:
if silent:
print(str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
print(str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
else:
print("\r \r"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
print("\n"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.25)
time.sleep(0.15)
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
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False):
global current_resource, resource_done, link, speed, show_phy_rates
from tempfile import TemporaryFile
targetloglevel = 3+verbosity-quietness
show_phy_rates = phy_rates
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
@@ -507,14 +614,14 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
print("Filename exceeds max size, cannot send")
exit(1)
else:
print("Preparing file...", end=" ")
print("Preparing file...", end=es)
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
print("\r \r", end="")
print(f"{erase_str}", end="")
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
@@ -537,7 +644,7 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
else:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=es)
sys.stdout.flush()
i = 0
@@ -554,13 +661,13 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("Path not found")
else:
print("\r \rPath not found")
print(f"{erase_str}Path not found")
exit(1)
else:
if silent:
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
print(f"{erase_str}Establishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=es)
receiver_identity = RNS.Identity.recall(destination_hash)
receiver_destination = RNS.Destination(
@@ -583,19 +690,19 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
else:
print("\r \rLink establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
print(f"{erase_str}Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
exit(1)
elif not RNS.Transport.has_path(destination_hash):
if silent:
print("No path found to "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rNo path found to "+RNS.prettyhexrep(destination_hash))
print(f"{erase_str}No path found to "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
if silent:
print("Advertising file resource...")
else:
print("\r \rAdvertising file resource ", end=" ")
print(f"{erase_str}Advertising file resource ", end=es)
link.identify(identity)
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
@@ -613,35 +720,54 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("File was not accepted by "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
print(f"{erase_str}File was not accepted by "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
if silent:
print("Transferring file...")
else:
print("\r \rTransferring file ", end=" ")
print(f"{erase_str}Transferring file ", end=es)
def progress_update(i, done=False):
time.sleep(0.1)
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
if show_phy_rates:
pss = size_str(phy_speed, "b")
phy_str = f" ({pss}ps at physical layer)"
else:
phy_str = ""
es = " "
cs = size_str(int(prg*current_resource.total_size))
ts = size_str(current_resource.total_size)
ss = size_str(speed, "b")
stat_str = f"{percent}% - {cs} of {ts} - {ss}ps{phy_str}"
if not done:
print(f"{erase_str}Transferring file "+syms[i]+" "+stat_str, end=es)
else:
print(f"{erase_str}Transfer complete "+stat_str, end=es)
sys.stdout.flush()
i = (i+1)%len(syms)
return i
while not resource_done:
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)
i = progress_update(i)
if not silent:
i = progress_update(i, done=True)
if current_resource.status != RNS.Resource.COMPLETE:
if silent:
print("The transfer failed")
else:
print("\r \rThe transfer failed")
print(f"{erase_str}The transfer failed")
exit(1)
else:
if silent:
print(str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
else:
print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
print("\n"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.25)
real_file.close()
@@ -658,12 +784,16 @@ def main():
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
parser.add_argument("-S", '--silent', action='store_true', default=False, help="disable transfer progress output")
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming transfer requests")
parser.add_argument("-F", '--allow-fetch', action='store_true', default=False, help="allow authenticated clients to fetch files")
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
parser.add_argument("-j", "--jail", metavar="path", action="store", default=None, help="restrict fetch requests to specified path", type=str)
parser.add_argument("-s", "--save", metavar="path", action="store", default=None, help="save received files in specified path", type=str)
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="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('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity (or add in ~/.rncp/allowed_identities)", type=str)
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept requests from anyone")
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="sender timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
parser.add_argument('-P', '--phy-rates', action='store_true', default=False, help="display physical layer transfer rates")
# parser.add_argument("--limit", action="store", metavar="files", type=float, help="maximum number of files to accept", default=None)
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
@@ -675,6 +805,9 @@ def main():
verbosity=args.verbose,
quietness=args.quiet,
allowed = args.allowed,
fetch_allowed = args.allow_fetch,
jail = args.jail,
save = args.save,
display_identity=args.print_identity,
# limit=args.limit,
disable_auth=args.no_auth,
@@ -691,6 +824,8 @@ def main():
file = args.file,
timeout = args.w,
silent = args.silent,
phy_rates = args.phy_rates,
save = args.save,
)
else:
print("")
@@ -706,6 +841,7 @@ def main():
file = args.file,
timeout = args.w,
silent = args.silent,
phy_rates = args.phy_rates,
)
else:
+89 -8
View File
@@ -64,19 +64,22 @@ def main():
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument("-i", "--identity", metavar="identity", action="store", default=None, help="hexadecimal Reticulum Destination hash or path to Identity file", type=str)
parser.add_argument("-g", "--generate", metavar="path", action="store", default=None, help="generate a new Identity")
parser.add_argument("-g", "--generate", metavar="file", action="store", default=None, help="generate a new Identity")
parser.add_argument("-m", "--import", dest="import_str", metavar="identity_data", action="store", default=None, help="import Reticulum identity in hex, base32 or base64 format", type=str)
parser.add_argument("-x", "--export", action="store_true", default=None, help="export identity to hex, base32 or base64 format")
parser.add_argument("-v", "--verbose", action="count", default=0, help="increase verbosity")
parser.add_argument("-q", "--quiet", action="count", default=0, help="decrease verbosity")
parser.add_argument("-a", "--announce", metavar="aspects", action="store", default=None, help="announce a destination based on this Identity")
parser.add_argument("-H", "--hash", metavar="aspects", action="store", default=None, help="show destination 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("-e", "--encrypt", metavar="file", action="store", default=None, help="encrypt file")
parser.add_argument("-d", "--decrypt", metavar="file", action="store", default=None, help="decrypt file")
parser.add_argument("-s", "--sign", metavar="path", action="store", default=None, help="sign file")
parser.add_argument("-V", "--validate", metavar="path", action="store", default=None, help="validate signature")
parser.add_argument("-r", "--read", metavar="path", action="store", default=None, help="input file path", type=str)
parser.add_argument("-w", "--write", metavar="path", action="store", default=None, help="output file path", type=str)
parser.add_argument("-r", "--read", metavar="file", action="store", default=None, help="input file path", type=str)
parser.add_argument("-w", "--write", metavar="file", action="store", default=None, help="output file path", type=str)
parser.add_argument("-f", "--force", action="store_true", default=None, help="write output even if it overwrites existing files")
parser.add_argument("-I", "--stdin", action="store_true", default=False, help=argparse.SUPPRESS) # "read input from STDIN instead of file"
parser.add_argument("-O", "--stdout", action="store_true", default=False, help=argparse.SUPPRESS) # help="write output to STDOUT instead of file",
@@ -86,7 +89,8 @@ def main():
parser.add_argument("-p", "--print-identity", action="store_true", default=False, help="print identity info and exit")
parser.add_argument("-P", "--print-private", action="store_true", default=False, help="allow displaying private keys")
parser.add_argument("-b", "--base64", action="store_true", default=False, help=argparse.SUPPRESS) # help="Use base64-encoded input and output")
parser.add_argument("-b", "--base64", action="store_true", default=False, help="Use base64-encoded input and output")
parser.add_argument("-B", "--base32", action="store_true", default=False, help="Use base32-encoded input and output")
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
@@ -110,6 +114,59 @@ def main():
args.read = args.sign
identity_str = args.identity
if args.import_str:
identity_bytes = None
try:
if args.base64:
identity_bytes = base64.urlsafe_b64decode(args.import_str)
elif args.base32:
identity_bytes = base64.b32decode(args.import_str)
else:
identity_bytes = bytes.fromhex(args.import_str)
except Exception as e:
print("Invalid identity data specified for import: "+str(e))
exit(41)
try:
identity = RNS.Identity.from_bytes(identity_bytes)
except Exception as e:
print("Could not create Reticulum identity from specified data: "+str(e))
exit(42)
RNS.log("Identity imported")
if args.base64:
RNS.log("Public Key : "+base64.urlsafe_b64encode(identity.get_public_key()).decode("utf-8"))
elif args.base32:
RNS.log("Public Key : "+base64.b32encode(identity.get_public_key()).decode("utf-8"))
else:
RNS.log("Public Key : "+RNS.hexrep(identity.get_public_key(), delimit=False))
if identity.prv:
if args.print_private:
if args.base64:
RNS.log("Private Key : "+base64.urlsafe_b64encode(identity.get_private_key()).decode("utf-8"))
elif args.base32:
RNS.log("Private Key : "+base64.b32encode(identity.get_private_key()).decode("utf-8"))
else:
RNS.log("Private Key : "+RNS.hexrep(identity.get_private_key(), delimit=False))
else:
RNS.log("Private Key : Hidden")
if args.write:
try:
wp = os.path.expanduser(args.write)
if not os.path.isfile(wp) or args.force:
identity.to_file(wp)
RNS.log("Wrote imported identity to "+str(args.write))
else:
print("File "+str(wp)+" already exists, not overwriting")
exit(43)
except Exception as e:
print("Error while writing imported identity to file: "+str(e))
exit(44)
exit(0)
if not args.generate and not identity_str:
print("\nNo identity provided, cannot continue\n")
parser.print_help()
@@ -246,14 +303,38 @@ def main():
exit(0)
if args.print_identity:
RNS.log("Public Key : "+RNS.hexrep(identity.pub_bytes, delimit=False))
if args.base64:
RNS.log("Public Key : "+base64.urlsafe_b64encode(identity.get_public_key()).decode("utf-8"))
elif args.base32:
RNS.log("Public Key : "+base64.b32encode(identity.get_public_key()).decode("utf-8"))
else:
RNS.log("Public Key : "+RNS.hexrep(identity.get_public_key(), delimit=False))
if identity.prv:
if args.print_private:
RNS.log("Private Key : "+RNS.hexrep(identity.prv_bytes, delimit=False))
if args.base64:
RNS.log("Private Key : "+base64.urlsafe_b64encode(identity.get_private_key()).decode("utf-8"))
elif args.base32:
RNS.log("Private Key : "+base64.b32encode(identity.get_private_key()).decode("utf-8"))
else:
RNS.log("Private Key : "+RNS.hexrep(identity.get_private_key(), delimit=False))
else:
RNS.log("Private Key : Hidden")
exit(0)
if args.export:
if identity.prv:
if args.base64:
RNS.log("Exported Identity : "+base64.urlsafe_b64encode(identity.get_private_key()).decode("utf-8"))
elif args.base32:
RNS.log("Exported Identity : "+base64.b32encode(identity.get_private_key()).decode("utf-8"))
else:
RNS.log("Exported Identity : "+RNS.hexrep(identity.get_private_key(), delimit=False))
else:
RNS.log("Identity doesn't hold a private key, cannot export")
exit(50)
exit(0)
if args.validate:
if not args.read and args.validate.lower().endswith("."+SIG_EXT):
args.read = str(args.validate).replace("."+SIG_EXT, "")
+1068 -285
View File
File diff suppressed because it is too large Load Diff
+269 -71
View File
@@ -23,14 +23,95 @@
# SOFTWARE.
import RNS
import os
import sys
import time
import argparse
from RNS._version import __version__
remote_link = None
def connect_remote(destination_hash, auth_identity, timeout, no_output = False):
global remote_link, reticulum
if not RNS.Transport.has_path(destination_hash):
if not no_output:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested", end=" ")
sys.stdout.flush()
RNS.Transport.request_path(destination_hash)
pr_time = time.time()
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
if time.time() - pr_time > timeout:
if not no_output:
print("\r \r", end="")
print("Path request timed out")
exit(12)
remote_identity = RNS.Identity.recall(destination_hash)
def remote_link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
if not no_output:
print("\r \r", end="")
print("The link timed out, exiting now")
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
if not no_output:
print("\r \r", end="")
print("The link was closed by the server, exiting now")
else:
if not no_output:
print("\r \r", end="")
print("Link closed unexpectedly, exiting now")
exit(10)
def remote_link_established(link):
global remote_link
link.identify(auth_identity)
remote_link = link
if not no_output:
print("\r \r", end="")
print("Establishing link with remote transport instance...", end=" ")
sys.stdout.flush()
remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management")
link = RNS.Link(remote_destination)
link.set_link_established_callback(remote_link_established)
link.set_link_closed_callback(remote_link_closed)
def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues,
drop_via, max_hops, remote=None, management_identity=None, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT,
no_output=False, json=False):
global remote_link, reticulum
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
if remote:
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(remote) != dest_len:
raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
identity_hash = bytes.fromhex(remote)
remote_hash = RNS.Destination.hash_from_name_and_identity("rnstransport.remote.management", identity_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
identity = RNS.Identity.from_file(os.path.expanduser(management_identity))
if identity == None:
raise ValueError("Could not load management identity from "+str(management_identity))
try:
connect_remote(remote_hash, identity, remote_timeout, no_output)
except Exception as e:
raise e
except Exception as e:
print(str(e))
exit(20)
while remote_link == None:
time.sleep(0.1)
def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues, drop_via):
if table:
destination_hash = None
if destination_hexhash != None:
@@ -46,23 +127,50 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
print(str(e))
sys.exit(1)
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
table = sorted(reticulum.get_path_table(), key=lambda e: (e["interface"], e["hops"]) )
if not remote_link:
table = sorted(reticulum.get_path_table(max_hops=max_hops), key=lambda e: (e["interface"], e["hops"]) )
else:
if not no_output:
print("\r \r", end="")
print("Sending request...", end=" ")
sys.stdout.flush()
receipt = remote_link.request("/path", data = ["table", destination_hash, max_hops])
while not receipt.concluded():
time.sleep(0.1)
response = receipt.get_response()
if response:
table = response
print("\r \r", end="")
else:
if not no_output:
print("\r \r", end="")
print("The remote request failed. Likely authentication failure.")
exit(10)
displayed = 0
for path in table:
if destination_hash == None or destination_hash == path["hash"]:
displayed += 1
exp_str = RNS.timestamp_str(path["expires"])
if path["hops"] == 1:
m_str = " "
else:
m_str = "s"
print(RNS.prettyhexrep(path["hash"])+" is "+str(path["hops"])+" hop"+m_str+" away via "+RNS.prettyhexrep(path["via"])+" on "+path["interface"]+" expires "+RNS.timestamp_str(path["expires"]))
if json:
import json
for p in table:
for k in p:
if isinstance(p[k], bytes):
p[k] = RNS.hexrep(p[k], delimit=False)
if destination_hash != None and displayed == 0:
print("No path known")
sys.exit(1)
print(json.dumps(table))
exit()
else:
for path in table:
if destination_hash == None or destination_hash == path["hash"]:
displayed += 1
exp_str = RNS.timestamp_str(path["expires"])
if path["hops"] == 1:
m_str = " "
else:
m_str = "s"
print(RNS.prettyhexrep(path["hash"])+" is "+str(path["hops"])+" hop"+m_str+" away via "+RNS.prettyhexrep(path["via"])+" on "+path["interface"]+" expires "+RNS.timestamp_str(path["expires"]))
if destination_hash != None and displayed == 0:
print("No path known")
sys.exit(1)
elif rates:
destination_hash = None
@@ -79,60 +187,99 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
print(str(e))
sys.exit(1)
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
table = sorted(reticulum.get_rate_table(), key=lambda e: e["last"] )
if len(table) == 0:
print("No information available")
if not remote_link:
table = reticulum.get_rate_table()
else:
displayed = 0
for entry in table:
if destination_hash == None or destination_hash == entry["hash"]:
displayed += 1
try:
last_str = pretty_date(int(entry["last"]))
start_ts = entry["timestamps"][0]
span = max(time.time() - start_ts, 3600.0)
span_hours = span/3600.0
span_str = pretty_date(int(entry["timestamps"][0]))
hour_rate = round(len(entry["timestamps"])/span_hours, 3)
if hour_rate-int(hour_rate) == 0:
hour_rate = int(hour_rate)
if entry["rate_violations"] > 0:
if entry["rate_violations"] == 1:
s_str = ""
else:
s_str = "s"
if not no_output:
print("\r \r", end="")
print("Sending request...", end=" ")
sys.stdout.flush()
receipt = remote_link.request("/path", data = ["rates", destination_hash])
while not receipt.concluded():
time.sleep(0.1)
response = receipt.get_response()
if response:
table = response
print("\r \r", end="")
else:
if not no_output:
print("\r \r", end="")
print("The remote request failed. Likely authentication failure.")
exit(10)
rv_str = ", "+str(entry["rate_violations"])+" active rate violation"+s_str
else:
rv_str = ""
if entry["blocked_until"] > time.time():
bli = time.time()-(int(entry["blocked_until"])-time.time())
bl_str = ", new announces allowed in "+pretty_date(int(bli))
else:
bl_str = ""
table = sorted(table, key=lambda e: e["last"])
if json:
import json
for p in table:
for k in p:
if isinstance(p[k], bytes):
p[k] = RNS.hexrep(p[k], delimit=False)
print(RNS.prettyhexrep(entry["hash"])+" last heard "+last_str+" ago, "+str(hour_rate)+" announces/hour in the last "+span_str+rv_str+bl_str)
except Exception as e:
print("Error while processing entry for "+RNS.prettyhexrep(entry["hash"]))
print(str(e))
if destination_hash != None and displayed == 0:
print(json.dumps(table))
exit()
else:
if len(table) == 0:
print("No information available")
sys.exit(1)
else:
displayed = 0
for entry in table:
if destination_hash == None or destination_hash == entry["hash"]:
displayed += 1
try:
last_str = pretty_date(int(entry["last"]))
start_ts = entry["timestamps"][0]
span = max(time.time() - start_ts, 3600.0)
span_hours = span/3600.0
span_str = pretty_date(int(entry["timestamps"][0]))
hour_rate = round(len(entry["timestamps"])/span_hours, 3)
if hour_rate-int(hour_rate) == 0:
hour_rate = int(hour_rate)
if entry["rate_violations"] > 0:
if entry["rate_violations"] == 1:
s_str = ""
else:
s_str = "s"
rv_str = ", "+str(entry["rate_violations"])+" active rate violation"+s_str
else:
rv_str = ""
if entry["blocked_until"] > time.time():
bli = time.time()-(int(entry["blocked_until"])-time.time())
bl_str = ", new announces allowed in "+pretty_date(int(bli))
else:
bl_str = ""
print(RNS.prettyhexrep(entry["hash"])+" last heard "+last_str+" ago, "+str(hour_rate)+" announces/hour in the last "+span_str+rv_str+bl_str)
except Exception as e:
print("Error while processing entry for "+RNS.prettyhexrep(entry["hash"]))
print(str(e))
if destination_hash != None and displayed == 0:
print("No information available")
sys.exit(1)
elif drop_queues:
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
RNS.log("Dropping announce queues on all interfaces...")
if remote_link:
if not no_output:
print("\r \r", end="")
print("Dropping announce queues on remote instances not yet implemented")
exit(255)
print("Dropping announce queues on all interfaces...")
reticulum.drop_announce_queues()
elif drop:
if remote_link:
if not no_output:
print("\r \r", end="")
print("Dropping path on remote instances not yet implemented")
exit(255)
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
@@ -145,17 +292,19 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
print(str(e))
sys.exit(1)
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
if reticulum.drop_path(destination_hash):
print("Dropped path to "+RNS.prettyhexrep(destination_hash))
else:
print("Unable to drop path to "+RNS.prettyhexrep(destination_hash)+". Does it exist?")
sys.exit(1)
elif drop_via:
if remote_link:
if not no_output:
print("\r \r", end="")
print("Dropping all paths via specific transport instance on remote instances yet not implemented")
exit(255)
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
@@ -168,17 +317,19 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
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:
if remote_link:
if not no_output:
print("\r \r", end="")
print("Requesting paths on remote instances not implemented")
exit(255)
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
@@ -191,9 +342,6 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity,
print(str(e))
sys.exit(1)
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
@@ -255,6 +403,16 @@ def main():
default=False
)
parser.add_argument(
"-m",
"--max",
action="store",
metavar="hops",
type=int,
help="maximum hops to filter path table by",
default=None
)
parser.add_argument(
"-r",
"--rates",
@@ -295,6 +453,41 @@ def main():
default=RNS.Transport.PATH_REQUEST_TIMEOUT
)
parser.add_argument(
"-R",
action="store",
metavar="hash",
help="transport identity hash of remote instance to manage",
default=None,
type=str
)
parser.add_argument(
"-i",
action="store",
metavar="path",
help="path to identity used for remote management",
default=None,
type=str
)
parser.add_argument(
"-W",
action="store",
metavar="seconds",
type=float,
help="timeout before giving up on remote queries",
default=RNS.Transport.PATH_REQUEST_TIMEOUT
)
parser.add_argument(
"-j",
"--json",
action="store_true",
help="output in JSON format",
default=False
)
parser.add_argument(
"destination",
nargs="?",
@@ -327,6 +520,11 @@ def main():
timeout = args.w,
drop_queues = args.drop_announces,
drop_via = args.drop_via,
max_hops = args.max,
remote=args.R,
management_identity=args.i,
remote_timeout=args.W,
json=args.json,
)
sys.exit(0)
+102 -77
View File
@@ -31,9 +31,9 @@ import argparse
from RNS._version import __version__
DEFAULT_PROBE_SIZE = 16
DEFAULT_TIMEOUT = 15
DEFAULT_TIMEOUT = 12
def program_setup(configdir, destination_hexhash, size=None, full_name = None, verbosity = 0, timeout=None):
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")
@@ -73,7 +73,7 @@ def program_setup(configdir, destination_hexhash, size=None, full_name = None, v
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
sys.stdout.flush()
_timeout = time.time() + (timeout or DEFAULT_TIMEOUT)
_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) and not time.time() > _timeout:
@@ -96,86 +96,106 @@ def program_setup(configdir, destination_hexhash, size=None, full_name = None, v
*aspects
)
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)
sent = 0
replies = 0
while probes:
receipt = probe.send()
if sent > 0:
time.sleep(wait)
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 = ""
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)
print("\rSent "+str(size)+" byte probe to "+RNS.prettyhexrep(destination_hash)+more+" ", end=" ")
receipt = probe.send()
sent += 1
_timeout = time.time() + (timeout or DEFAULT_TIMEOUT)
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)
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 = ""
if time.time() > _timeout:
print("\r \rProbe timed out")
print("\rSent probe "+str(sent)+" ("+str(size)+" bytes) to "+RNS.prettyhexrep(destination_hash)+more+" ", end=" ")
_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)
if time.time() > _timeout:
print("\r \rProbe timed out")
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)
print("\b\b ")
sys.stdout.flush()
if receipt.status == RNS.PacketReceipt.DELIVERED:
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)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dB]"
else:
if receipt.proof_packet != None:
if receipt.proof_packet.rssi != None:
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
if receipt.proof_packet.snr != None:
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
print(
"Valid reply received from "+
RNS.prettyhexrep(receipt.destination.hash)+
"\nRound-trip time is "+rttstring+
" over "+str(hops)+" hop"+ms+
reception_stats
)
else:
print("\r \rProbe timed out")
exit(2)
exit(0)
def main():
@@ -184,7 +204,9 @@ def main():
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)
@@ -208,7 +230,10 @@ def main():
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:
+29
View File
@@ -123,6 +123,18 @@ instance_control_port = 37429
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
# It is possible to allow remote management of Reticulum
# systems using the various built-in utilities, such as
# rnstatus and rnpath. You will need to specify one or
# more Reticulum Identity hashes for authenticating the
# queries from client programs. For this purpose, you can
# use existing identity files, or generate new ones with
# the rnid utility.
# enable_remote_management = yes
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
@@ -282,6 +294,23 @@ loglevel = 4
# Serial port for the device
port = /dev/ttyUSB0
# It is also possible to use BLE devices
# instead of wired serial ports. The
# target RNode must be paired with the
# host device before connecting. BLE
# devices can be connected by name,
# BLE MAC address or by any available.
# Connect to specific device by name
# port = ble://RNode 3B87
# Or by BLE MAC address
# port = ble://F4:12:73:29:4E:89
# Or connect to the first available,
# paired device
# port = ble://
# Set frequency to 867.2 MHz
frequency = 867200000
+194 -8
View File
@@ -23,6 +23,9 @@
# SOFTWARE.
import RNS
import os
import sys
import time
import argparse
from RNS._version import __version__
@@ -46,14 +49,134 @@ 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, json=False, astats=False, sorting=None, sort_reverse=False):
request_result = None
request_concluded = False
def get_remote_status(destination_hash, include_lstats, identity, no_output=False, timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
global request_result, request_concluded
link_count = None
if not RNS.Transport.has_path(destination_hash):
if not no_output:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested", end=" ")
sys.stdout.flush()
RNS.Transport.request_path(destination_hash)
pr_time = time.time()
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
if time.time() - pr_time > timeout:
if not no_output:
print("\r \r", end="")
print("Path request timed out")
exit(12)
remote_identity = RNS.Identity.recall(destination_hash)
def remote_link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
if not no_output:
print("\r \r", end="")
print("The link timed out, exiting now")
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
if not no_output:
print("\r \r", end="")
print("The link was closed by the server, exiting now")
else:
if not no_output:
print("\r \r", end="")
print("Link closed unexpectedly, exiting now")
exit(10)
def request_failed(request_receipt):
global request_result, request_concluded
if not no_output:
print("\r \r", end="")
print("The remote status request failed. Likely authentication failure.")
request_concluded = True
def got_response(request_receipt):
global request_result, request_concluded
response = request_receipt.response
if isinstance(response, list):
status = response[0]
if len(response) > 1:
link_count = response[1]
else:
link_count = None
request_result = (status, link_count)
request_concluded = True
def remote_link_established(link):
if not no_output:
print("\r \r", end="")
print("Sending request...", end=" ")
sys.stdout.flush()
link.identify(identity)
link.request("/status", data = [include_lstats], response_callback = got_response, failed_callback = request_failed)
if not no_output:
print("\r \r", end="")
print("Establishing link with remote transport instance...", end=" ")
sys.stdout.flush()
remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management")
link = RNS.Link(remote_destination)
link.set_link_established_callback(remote_link_established)
link.set_link_closed_callback(remote_link_closed)
while not request_concluded:
time.sleep(0.1)
if request_result != None:
print("\r \r", end="")
return request_result
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False,
lstats=False, sorting=None, sort_reverse=False, remote=None, management_identity=None,
remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
link_count = None
stats = None
try:
stats = reticulum.get_interface_stats()
except Exception as e:
pass
if remote:
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(remote) != dest_len:
raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
identity_hash = bytes.fromhex(remote)
destination_hash = RNS.Destination.hash_from_name_and_identity("rnstransport.remote.management", identity_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
identity = RNS.Identity.from_file(os.path.expanduser(management_identity))
if identity == None:
raise ValueError("Could not load management identity from "+str(management_identity))
try:
remote_status = get_remote_status(destination_hash, lstats, identity, no_output=json, timeout=remote_timeout)
if remote_status != None:
stats, link_count = remote_status
except Exception as e:
raise e
except Exception as e:
print(str(e))
exit(20)
else:
if lstats:
try:
link_count = reticulum.get_link_count()
except Exception as e:
pass
try:
stats = reticulum.get_interface_stats()
except Exception as e:
pass
if stats != None:
if json:
@@ -62,7 +185,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
if isinstance(stats[s], bytes):
stats[s] = RNS.hexrep(stats[s], delimit=False)
if isinstance(stats[s], dict):
if isinstance(stats[s], dict) or isinstance(stats[s], list):
for i in stats[s]:
if isinstance(i, dict):
for k in i:
@@ -169,6 +292,14 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "battery_percent" in ifstat and ifstat["battery_percent"] != None:
try:
bpi = int(ifstat["battery_percent"])
bss = ifstat["battery_state"]
print(f" Battery : {bpi}% ({bss})")
except:
pass
if "airtime_short" in ifstat and "airtime_long" in ifstat:
print(" Airtime : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
@@ -208,16 +339,32 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
lstr = ""
if link_count != None and lstats:
ms = "y" if link_count == 1 else "ies"
if "transport_id" in stats and stats["transport_id"] != None:
lstr = f", {link_count} entr{ms} in link table"
else:
lstr = f" {link_count} entr{ms} in link table"
if "transport_id" in stats and stats["transport_id"] != None:
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
if "probe_responder" in stats and stats["probe_responder"] != None:
print(" Probe responder at "+RNS.prettyhexrep(stats["probe_responder"])+ " active")
print(" Uptime is "+RNS.prettytime(stats["transport_uptime"]))
if "transport_uptime" in stats and stats["transport_uptime"] != None:
print(" Uptime is "+RNS.prettytime(stats["transport_uptime"])+lstr)
else:
if lstr != "":
print(f"\n{lstr}")
print("")
else:
print("Could not get RNS status")
if not remote:
print("Could not get RNS status")
else:
print("Could not get RNS status from remote transport instance "+RNS.prettyhexrep(identity_hash))
exit(1)
def main():
try:
@@ -241,6 +388,14 @@ def main():
default=False
)
parser.add_argument(
"-l",
"--link-stats",
action="store_true",
help="show link stats",
default=False,
)
parser.add_argument(
"-s",
"--sort",
@@ -266,6 +421,33 @@ def main():
default=False
)
parser.add_argument(
"-R",
action="store",
metavar="hash",
help="transport identity hash of remote instance to get status from",
default=None,
type=str
)
parser.add_argument(
"-i",
action="store",
metavar="path",
help="path to identity used for remote management",
default=None,
type=str
)
parser.add_argument(
"-w",
action="store",
metavar="seconds",
type=float,
help="timeout before giving up on remote queries",
default=RNS.Transport.PATH_REQUEST_TIMEOUT
)
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument("filter", nargs="?", default=None, help="only display interfaces with names including filter", type=str)
@@ -284,8 +466,12 @@ def main():
name_filter=args.filter,
json=args.json,
astats=args.announce_stats,
lstats=args.link_stats,
sorting=args.sort,
sort_reverse=args.reverse,
remote=args.R,
management_identity=args.i,
remote_timeout=args.w,
)
except KeyboardInterrupt:
+1 -1
View File
@@ -28,8 +28,8 @@ import argparse
import shlex
import time
import sys
import tty
import os
#import tty
from RNS._version import __version__
+177 -6
View File
@@ -144,6 +144,12 @@ def rand():
result = instance_random.random()
return result
def trace_exception(e):
import traceback
exception_info = "".join(traceback.TracebackException.from_exception(e).format())
log(f"An unhandled {str(type(e))} exception occurred: {str(e)}", LOG_ERROR)
log(exception_info, LOG_ERROR)
def hexrep(data, delimit=True):
try:
iter(data)
@@ -161,6 +167,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'
@@ -192,32 +201,56 @@ def prettyfrequency(hz, suffix="Hz"):
return "%.2f%s%s" % (num, last_unit, suffix)
def prettytime(time, verbose=False):
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 = ""
@@ -237,6 +270,53 @@ def prettytime(time, verbose=False):
else:
return tstr
def prettyshorttime(time, verbose=False, compact=False):
time = time*1e6
seconds = int(time // 1e6); time %= 1e6
milliseconds = int(time // 1e3); time %= 1e3
if compact:
microseconds = int(time)
else:
microseconds = round(time, 2)
ss = "" if seconds == 1 else "s"
sms = "" if milliseconds == 1 else "s"
sus = "" if microseconds == 1 else "s"
displayed = 0
components = []
if seconds > 0 and ((not compact) or displayed < 2):
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s")
displayed += 1
if milliseconds > 0 and ((not compact) or displayed < 2):
components.append(str(milliseconds)+" millisecond"+sms if verbose else str(milliseconds)+"ms")
displayed += 1
if microseconds > 0 and ((not compact) or displayed < 2):
components.append(str(microseconds)+" microsecond"+sus if verbose else str(microseconds)+"µs")
displayed += 1
i = 0
tstr = ""
for c in components:
i += 1
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
tstr += c
if tstr == "":
return "0us"
else:
return tstr
def phyparams():
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
print("Plaintext Packet MDU : "+str(Packet.PLAIN_MDU)+" bytes")
@@ -252,3 +332,94 @@ def panic():
def exit():
print("")
sys.exit(0)
profiler_ran = False
profiler_tags = {}
def profiler(tag=None, capture=False, super_tag=None):
global profiler_ran, profiler_tags
try:
thread_ident = threading.get_ident()
if capture:
end = time.perf_counter()
if tag in profiler_tags and thread_ident in profiler_tags[tag]["threads"]:
if profiler_tags[tag]["threads"][thread_ident]["current_start"] != None:
begin = profiler_tags[tag]["threads"][thread_ident]["current_start"]
profiler_tags[tag]["threads"][thread_ident]["current_start"] = None
profiler_tags[tag]["threads"][thread_ident]["captures"].append(end-begin)
if not profiler_ran:
profiler_ran = True
else:
if not tag in profiler_tags:
profiler_tags[tag] = {"threads": {}, "super": super_tag}
if not thread_ident in profiler_tags[tag]["threads"]:
profiler_tags[tag]["threads"][thread_ident] = {"current_start": None, "captures": []}
profiler_tags[tag]["threads"][thread_ident]["current_start"] = time.perf_counter()
except Exception as e:
trace_exception(e)
def profiler_results():
from statistics import mean, median, stdev
results = {}
for tag in profiler_tags:
tag_captures = []
tag_entry = profiler_tags[tag]
for thread_ident in tag_entry["threads"]:
thread_entry = tag_entry["threads"][thread_ident]
thread_captures = thread_entry["captures"]
sample_count = len(thread_captures)
if sample_count > 2:
thread_results = {
"count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": stdev(thread_captures)
}
tag_captures.extend(thread_captures)
sample_count = len(tag_captures)
if sample_count > 2:
tag_results = {
"name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": stdev(tag_captures)
}
results[tag] = tag_results
def print_results_recursive(tag, results, level=0):
print_tag_results(tag, level+1)
for tag_name in results:
sub_tag = results[tag_name]
if sub_tag["super"] == tag["name"]:
print_results_recursive(sub_tag, results, level=level+1)
def print_tag_results(tag, level):
ind = " "*level
name = tag["name"]; count = tag["count"]
mean = tag["mean"]; tag["median"]; stdev = tag["stdev"]
print(f"{ind}{name}")
print(f"{ind} Samples : {count}")
print(f"{ind} Mean : {prettyshorttime(mean)}")
print(f"{ind} Median : {prettyshorttime(median)}")
print(f"{ind} St.dev. : {prettyshorttime(stdev)}")
print("")
print("\nProfiler results:\n")
for tag_name in results:
tag = results[tag_name]
if tag["super"] == None:
print_results_recursive(tag, results)
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.6.2"
__version__ = "0.8.4"
+19
View File
@@ -11,6 +11,25 @@ def interfaces() -> List[str]:
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
return [a.name for a in adapters]
def interface_names_to_indexes() -> dict:
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
results = {}
for adapter in adapters:
results[adapter.name] = adapter.index
return results
def interface_name_to_nice_name(ifname) -> str:
try:
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
for adapter in adapters:
if adapter.name == ifname:
if hasattr(adapter, "nice_name"):
return adapter.nice_name
except:
return None
return None
def ifaddresses(ifname) -> dict:
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
ifa = {}
+12 -15
View File
@@ -14,12 +14,15 @@ This document outlines the currently established development roadmap for Reticul
## Currently Active Work Areas
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
- The current `0.6.x` release cycle aims at completing
- The current `0.8.x` release cycle aims at completing
- [ ] Hot-pluggable interface system
- [ ] External interface plugins
- [ ] Network-wide path balancing and multi-pathing
- [ ] Expanded hardware support
- [ ] Overhauling and updating the documentation
- [ ] Distributed Destination Naming System
- [ ] Create a standalone RNS Daemon app for Android
- [ ] Network-wide path balancing
- [ ] Add automatic retries to all use cases of the `Request` API
- [ ] A standalone RNS Daemon app for Android
- [ ] Addding automatic retries to all use cases of the `Request` API
- [ ] Performance and memory optimisations of the Python reference implementation
- [ ] Fixing bugs discovered while operating Reticulum systems and applications
@@ -35,17 +38,9 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
- Update announce description
- Add in-depth explanation of the IFAC system
- Software
- Update Sideband screenshots
- Update Sideband description
- Update NomadNet screenshots
- Update Sideband screenshots
- Installation
- [x] Add a *Reticulum On Raspberry Pi* section
- [x] Update *Reticulum On Android* section if necessary
- [x] Update Android install documentation.
- Update software descriptions and screenshots
- Communications hardware section
- Add information about RNode external displays.
- [x] Packet radio modems.
- Possibly add other relevant types here as well.
- Setup *Best Practices For...* / *Installation Examples* section.
- Home or office (example)
@@ -65,6 +60,8 @@ These efforts seek to broaden the universality of the Reticulum software and har
### Functionality
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
- Add support for user-supplied external interface drivers
- Add interface hot-plug and live up/down control to running instances
- Add automatic retries to all use cases of the `Request` API
- Network-wide path balancing
- Distributed Destination Naming System
@@ -82,10 +79,10 @@ These effors seek to make Reticulum easier to use and operate, and to expand the
### Interfaceability
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
- Filesystem interface
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
- More LoRa transceivers
- AT-compatible modems
- Filesystem interface
- Direct SDR Support
- Optical mediums
- IR Transceivers
@@ -105,7 +102,7 @@ The Reticulum ecosystem is enriched by several other software and hardware proje
This section lists, in no particular order, various important efforts that would be beneficial to the goals of Reticulum.
- The [RNode](https://unsigned.io/rnode/) project
- [ ] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
- [x] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
## Release History
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -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: 46393e4581f8a313437c181a16088a4d
config: ab3a817981de13f74ede3061942f75d2
tags: 645f666f9bcd5a90fca523b33c5a78b7

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

+44 -14
View File
@@ -71,7 +71,8 @@ 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.
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
such as LoRa or packet radio.
Nomad Network
^^^^^^^^^^^^^
@@ -109,7 +110,7 @@ Sideband
If you would rather use a program with a graphical user interface, you can take
a look at `Sideband <https://unsigned.io/sideband>`_, which is available for Android,
Linux and macOS.
Linux, macOS and Windows.
.. only:: html
@@ -128,6 +129,28 @@ systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted Q
Paper Messages, or anything else Reticulum supports. It also interoperates with
the Nomad Network program.
MeshChat
^^^^^^^^
The `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ application
is a user-friendly LXMF client for macOS and Windows, that also includes voice call
functionality, and a range of other interesting functions.
.. only:: html
.. image:: screenshots/meshchat_1.webp
:align: center
:target: _images/meshchat_1.webp
.. only:: latexpdf
.. image:: screenshots/meshchat_1.png
:align: center
:target: _images/meshchat_1.png
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.
Using the Included Utilities
=============================================
Reticulum comes with a range of included utilities that make it easier to
@@ -180,29 +203,29 @@ and :ref:`Interfaces<interfaces-main>` chapters of this manual.
Connecting Reticulum Instances Over the Internet
================================================
Reticulum currently offers two interfaces suitable for connecting instances over the Internet: :ref:`TCP<interfaces-tcps>`
and :ref:`I2P<interfaces-i2p>`. Each interface offers a different set of features, and Reticulum
users should carefully choose the interface which best suites their needs.
and :ref:`I2P<interfaces-i2p>`. Each interface offers a different set of features, and Reticulum
users should carefully choose the interface which best suites their needs.
The ``TCPServerInterface`` allows users to host an instance accessible over TCP/IP. This
method is generally faster, lower latency, and more energy efficient than using ``I2PInterface``,
however it also leaks more data about the server host.
TCP connections reveal the IP address of both your instance and the server to anyone who can
inspect the connection. Someone could use this information to determine your location or identity. Adversaries
inspect the connection. Someone could use this information to determine your location or identity. Adversaries
inspecting your packets may be able to record packet metadata like time of transmission and packet size.
Even though Reticulum encrypts traffic, TCP does not, so an adversary may be able to use
packet inspection to learn that a system is running Reticulum, and what other IP addresses connect to it.
Hosting a publicly reachable instance over TCP also requires a publicly reachable IP address,
which most Internet connections don't offer anymore.
The ``I2PInterface`` routes messages through the `Invisible Internet Protocol
The ``I2PInterface`` routes messages through the `Invisible Internet Protocol
(I2P) <https://geti2p.net/en/>`_. To use this interface, users must also run an I2P daemon in
parallel to ``rnsd``. For always-on I2P nodes it is recommended to use `i2pd <https://i2pd.website/>`_.
parallel to ``rnsd``. For always-on I2P nodes it is recommended to use `i2pd <https://i2pd.website/>`_.
By default, I2P will encrypt and mix all traffic sent over the Internet, and
hide both the sender and receiver Reticulum instance IP addresses. Running an I2P node
By default, I2P will encrypt and mix all traffic sent over the Internet, and
hide both the sender and receiver Reticulum instance IP addresses. Running an I2P node
will also relay other I2P user's encrypted packets, which will use extra
bandwidth and compute power, but also makes timing attacks and other forms of
bandwidth and compute power, but also makes timing attacks and other forms of
deep-packet-inspection much more difficult.
I2P also allows users to host globally available Reticulum instances from non-public IP's and behind firewalls and NAT.
@@ -231,7 +254,7 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
[[RNS Testnet BetweenTheBorders]]
type = TCPClientInterface
enabled = yes
target_host = betweentheborders.com
target_host = reticulum.betweentheborders.com
target_port = 4242
# Interface to Testnet I2P Hub
@@ -244,6 +267,13 @@ Many other Reticulum instances are connecting to this testnet, and you can also
via other entry points if you know them. There is absolutely no control over the network
topography, usage or what types of instances connect. It will also occasionally be used
to test various failure scenarios, and there are no availability or service guarantees.
Expect weird things to happen on this network, as people experiment and try out things.
It probably goes without saying, but *don't use the testnet entry-points as
hardcoded or default interfaces in any applications you ship to users*. When
shipping applications, the best practice is to provide your own default
connectivity solutions, if needed and applicable, or in most cases, simply
leave it up to the user which networks to connect to, and how.
Adding Radio Interfaces
@@ -415,7 +445,7 @@ locally on your device using the following command:
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.
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and starting point.
ARM64
@@ -455,7 +485,7 @@ for including and using Reticulum in your own scripts and programs.
.. code::
# Install pipx
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
@@ -489,7 +519,7 @@ for including and using Reticulum in your own scripts and programs.
.. code::
# Install pipx
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
+118 -46
View File
@@ -90,77 +90,151 @@ Supported Boards
To create one or more RNodes, you will need to obtain supported development
boards. The following boards are supported by the auto-installer.
LilyGO LoRa32 v2.1
""""""""""""""""""
.. image:: graphics/board_t3v21.png
:width: 46%
------------
.. image:: graphics/board_tbeam_supreme.png
:width: 75%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
LilyGO LoRa32 v2.0
""""""""""""""""""
.. image:: graphics/board_t3v20.png
:width: 46%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
LilyGO T-Beam
LilyGO T-Beam Supreme
"""""""""""""
- **Transceiver IC** Semtech SX1262, SX1268
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
.. image:: graphics/board_tbeam.png
:width: 75%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
LilyGO T-Beam
"""""""""""""
- **Transceiver IC** Semtech SX1262, SX1268, SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
Heltec LoRa32 v2.0
""""""""""""""""""
.. image:: graphics/board_heltec32.png
:width: 58%
.. image:: graphics/board_t3s3.png
:width: 50%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
LilyGO T3S3
"""""""""""
- **Transceiver IC** Semtech SX1262, SX1268, SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
.. image:: graphics/board_rak4631.png
:width: 45%
:align: center
RAK4631-based Boards
""""""""""""""""""""
- **Transceiver IC** Semtech SX1262, SX1268
- **Device Platform** nRF52
- **Manufacturer** `RAK Wireless <https://www.rakwireless.com>`_
------------
.. image:: graphics/board_rnodev2.png
:width: 68%
:align: center
Unsigned RNode v2.x
"""""""""""""""""""
.. image:: graphics/board_rnodev2.png
:width: 58%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Transceiver IC** Semtech SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
------------
.. image:: graphics/board_t3v21.png
:width: 46%
:align: center
LilyGO LoRa32 v2.1
""""""""""""""""""
- **Transceiver IC** Semtech SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
.. image:: graphics/board_t3v20.png
:width: 46%
:align: center
LilyGO LoRa32 v2.0
""""""""""""""""""
- **Transceiver IC** Semtech SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
.. image:: graphics/board_t3v10.png
:width: 46%
:align: center
LilyGO LoRa32 v1.0
""""""""""""""""""
- **Transceiver IC** Semtech SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
.. image:: graphics/board_tdeck.png
:width: 45%
:align: center
LilyGO T-Deck
"""""""""""""
- **Transceiver IC** Semtech SX1262, SX1268
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
------------
.. image:: graphics/board_heltec32v30.png
:width: 58%
:align: center
Heltec LoRa32 v3.0
""""""""""""""""""
- **Transceiver IC** Semtech SX1262 and SX1268
- **Device Platform** ESP32
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
------------
.. image:: graphics/board_heltec32v20.png
:width: 58%
:align: center
Heltec LoRa32 v2.0
""""""""""""""""""
- **Transceiver IC** Semtech SX1276 and SX1278
- **Device Platform** ESP32
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
------------
Unsigned RNode v1.x
"""""""""""""""""""
.. image:: graphics/board_rnode.png
:width: 50%
:align: center
- **Supported Firmware Lines** v1.x
- **Transceiver IC** Semtech SX1276
Unsigned RNode v1.x
"""""""""""""""""""
- **Transceiver IC** Semtech SX1276 and SX1278
- **Device Platform** AVR ATmega1284p
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
------------
.. _rnode-installation:
@@ -194,10 +268,8 @@ Usage with Reticulum
^^^^^^^^^^^^^^^^^^^^
When the devices have been installed and provisioned, you can use them with Reticulum
by adding the :ref:`relevant interface section<interfaces-rnode>` to the configuration
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
RNode, using the parameters stored in the RNode itself.
file of Reticulum. In the configuraion you can specify all interface parameters,
such as serial port and on-air parameters.
WiFi-based Hardware
+152 -3
View File
@@ -33,9 +33,20 @@ system, which should be enabled by default in almost all OSes.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
# This example demonstrates a bare-minimum setup
# of an Auto Interface. It will allow communica-
# tion with all other reachable devices on all
# usable physical ethernet-based devices that
# are available on the system.
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# This example demonstrates an more specifically
# configured Auto Interface, that only uses spe-
# cific physical interfaces, and has a number of
# other configuration options set.
[[Default Interface]]
type = AutoInterface
@@ -47,6 +58,12 @@ system, which should be enabled by default in almost all OSes.
group_id = reticulum
# You can also choose the multicast address type:
# temporary (default, Temporary Multicast Address)
# or permanent (Permanent Multicast Address)
multicast_address_type = permanent
# You can also select specifically which
# kernel networking devices to use.
@@ -341,6 +358,23 @@ can be used, and offers full control over LoRa parameters.
# Serial port for the device
port = /dev/ttyUSB0
# It is also possible to use BLE devices
# instead of wired serial ports. The
# target RNode must be paired with the
# host device before connecting. BLE
# devices can be connected by name,
# BLE MAC address or by any available.
# Connect to specific device by name
# port = ble://RNode 3B87
# Or by BLE MAC address
# port = ble://F4:12:73:29:4E:89
# Or connect to the first available,
# paired device
# port = ble://
# Set frequency to 867.2 MHz
frequency = 867200000
@@ -389,6 +423,121 @@ can be used, and offers full control over LoRa parameters.
# airtime_limit_short = 33
.. _interfaces-rnode-multi:
RNode Multi Interface
=====================
For RNodes that support multiple LoRa transceivers, the RNode
Multi interface can be used to configure sub-interfaces individually.
.. code::
# Here's an example of how to add an RNode Multi interface
# using the RNode LoRa transceiver.
[[RNode Multi Interface]]
type = RNodeMultiInterface
# Enable interface if you want to use it!
interface_enabled = True
# Serial port for the device
port = /dev/ttyACM0
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters.
# id_callsign = MYCALL-0
# id_interval = 600
# A subinterface
[[[HIGHDATARATE]]]
# Subinterfaces can be enabled and disabled in of themselves
interface_enabled = True
# Set frequency to 2.4GHz
frequency = 2400000000
# Set LoRa bandwidth to 1625 KHz
bandwidth = 1625000
# Set TX power to 0 dBm (0.12 mW)
txpower = 0
# The virtual port, only the manufacturer
# or the person who wrote the board config
# can tell you what it will be for which
# physical hardware interface
vport = 1
# Select spreading factor 5. Valid
# range is 5 through 12, with 5
# being the fastest and 12 having
# the longest range.
spreadingfactor = 5
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# It is possible to limit the airtime
# utilisation of an RNode by using the
# following two configuration options.
# The short-term limit is applied in a
# window of approximately 15 seconds,
# and the long-term limit is enforced
# over a rolling 60 minute window. Both
# options are specified in percent.
# airtime_limit_long = 100
# airtime_limit_short = 100
[[[LOWDATARATE]]]
# Subinterfaces can be enabled and disabled in of themselves
interface_enabled = True
# Set frequency to 865.6 MHz
frequency = 865600000
# The virtual port, only the manufacturer
# or the person who wrote the board config
# can tell you what it will be for which
# physical hardware interface
vport = 0
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 0 dBm (0.12 mW)
txpower = 0
# Select spreading factor 7. Valid
# range is 5 through 12, with 5
# being the fastest and 12 having
# the longest range.
spreadingfactor = 7
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# It is possible to limit the airtime
# utilisation of an RNode by using the
# following two configuration options.
# The short-term limit is applied in a
# window of approximately 15 seconds,
# and the long-term limit is enforced
# over a rolling 60 minute window. Both
# options are specified in percent.
# airtime_limit_long = 100
# airtime_limit_short = 100
.. _interfaces-serial:
Serial Interface
+1 -1
View File
@@ -60,7 +60,7 @@ with Reticulum:
* | Reticulum is designed to work reliably in open, trustless environments. This
means you can use it to create open-access networks, where participants can
join and leave in an free and unorganised manner. This property allows an
join and leave in a free and unorganised manner. This property allows an
entirely new, and so far, mostly unexplored class of networked applications,
where networks, and the information flow within them can form and dissolve
organically.
+21 -13
View File
@@ -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**
@@ -134,10 +134,11 @@ be sufficient, even far into the future.
By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
channel to a destination, called a *Link*. Both data sent over Links and single packets offer
*Initiator Anonymity*, and links additionally offer *Forward Secrecy* by using an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
coordination, verification and reliability layers are fully autonomous and also based on elliptic
curve cryptography.
*Initiator Anonymity*. Links additionally offer *Forward Secrecy* by default, employing an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
layers are fully autonomous and also based on elliptic curve cryptography.
Reticulum also offers symmetric key encryption for group-oriented communications, as well as
unencrypted packets for local broadcast purposes.
@@ -431,7 +432,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
* | A packet is always created with an associated destination and some payload data. When the packet is sent
to a *single* destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the destination's public key, and encrypt the information.
an ECDH key exchange with the destination's public key (or ratchet key, if available), and encrypt the information.
* | It is important to note that this key exchange does not require any network traffic. The sender already
knows the public key of the destination from an earlier received *announce*, and can thus perform the ECDH
@@ -595,7 +596,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.
@@ -693,7 +694,8 @@ Wire Format
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
[Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
@@ -725,12 +727,16 @@ Wire Format
type 2 1 Two byte header, two 16 byte address fields
Context Flag
-----------------
unset 0 The context flag is used for various types
set 1 of signalling, depending on packet context
Propagation Types
-----------------
broadcast 00
transport 01
reserved 10
reserved 11
broadcast 0
transport 1
Destination Types
@@ -862,12 +868,14 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
* HKDF for key derivation
* Fernet for encrypted tokens
* Modified Fernet for encrypted tokens
* AES-128 in CBC mode
* HMAC for message authentication
* No Version and Timestamp metadata included
* SHA-256
* SHA-512
+142 -27
View File
@@ -312,8 +312,9 @@ Filter output to only show some interfaces:
.. code:: text
usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
[-r] [-j] [-v] [filter]
usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-A]
[-l] [-s SORT] [-r] [-j] [-R hash] [-i path]
[-w seconds] [-v] [filter]
Reticulum Network Stack Status
@@ -326,9 +327,13 @@ Filter output to only show some interfaces:
--version show program's version number and exit
-a, --all show all interfaces
-A, --announce-stats show announce stats
-l, --link-stats show link stats
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
-r, --reverse reverse sorting
-j, --json output in JSON format
-R hash transport identity hash of remote instance to get status from
-i path path to identity used for remote management
-w seconds timeout before giving up on remote queries
-v, --verbose
@@ -452,8 +457,9 @@ Resolve path to a destination:
.. code:: text
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
[-x] [-w seconds] [-v] [destination]
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-m hops]
[-r] [-d] [-D] [-x] [-w seconds] [-R hash] [-i path]
[-W seconds] [-j] [-v] [destination]
Reticulum Path Discovery Utility
@@ -465,11 +471,16 @@ Resolve path to a destination:
--config CONFIG path to alternative Reticulum config directory
--version show program's version number and exit
-t, --table show all known paths
-m hops, --max hops maximum hops to filter path table by
-r, --rates show announce rate info
-d, --drop remove the path to a destination
-D, --drop-announces drop all queued announces
-x, --drop-via drop all paths via specified transport instance
-w seconds timeout before giving up
-R hash transport identity hash of remote instance to manage
-i path path to identity used for remote management
-W seconds timeout before giving up on remote queries
-j, --json output in JSON format
-v, --verbose
@@ -524,20 +535,27 @@ these as part of the result as well.
.. code:: text
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
usage: rnprobe [-h] [--config CONFIG] [-s SIZE] [-n PROBES]
[-t seconds] [-w seconds] [--version] [-v]
[full_name] [destination_hash]
Reticulum Probe Utility
positional arguments:
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
options:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-s SIZE, --size SIZE size of probe packet payload in bytes
--version show program's version number and exit
-n PROBES, --probes PROBES
number of probes to send
-t seconds, --timeout seconds
timeout before giving up
-w seconds, --wait seconds
time between each probe
--version show program's version number and exit
-v, --verbose
@@ -578,8 +596,9 @@ Or fetch a file from the remote system:
.. code:: text
usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
usage: rncp [-h] [--config path] [-v] [-q] [-S] [-l] [-F] [-f]
[-j path] [-b seconds] [-a allowed_hash] [-n] [-p]
[-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
@@ -594,10 +613,12 @@ Or fetch a file from the remote system:
-q, --quiet decrease verbosity
-S, --silent disable transfer progress output
-l, --listen listen for incoming transfer requests
-F, --allow-fetch allow authenticated clients to fetch files
-f, --fetch fetch file from remote listener instead of sending
-j path, --jail path restrict fetch requests to specified path
-b seconds announce interval, 0 to only announce at startup
-a allowed_hash accept from this identity
-n, --no-auth accept files and fetches from anyone
-a allowed_hash allow this identity
-n, --no-auth accept requests from anyone
-p, --print-identity print identity and destination info and exit
-w seconds sender timeout before giving up
--version show program's version number and exit
@@ -685,15 +706,19 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
.. code:: text
usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
[--trust-key hexbytes] [--version] [port]
usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version]
[--fw-url url] [--nocheck] [-e] [-E] [-C]
[--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
[--display-addr byte] [--freq Hz] [--bw Hz] [--txp dBm]
[--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump]
[--eeprom-wipe] [-P] [--trust-key hexbytes] [--version] [-f]
[-r] [-k] [-S] [-H FIRMWARE_HASH] [--platform platform]
[--product product] [--model model] [--hwrev revision]
[port]
RNode Configuration and firmware utility. This program allows you to change various
settings and startup modes of RNode. It can also install, flash and update the firmware
on supported devices.
RNode Configuration and firmware utility. This program allows you to change
various settings and startup modes of RNode. It can also install, flash and
update the firmware on supported devices.
positional arguments:
port serial port where RNode is attached
@@ -703,20 +728,26 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
-i, --info Show device info
-a, --autoinstall Automatic installation on various supported devices
-u, --update Update firmware to the latest version
-U, --force-update Update to specified firmware even if version matches or is older than installed version
--fw-version version Use a specific firmware version for update or autoinstall
-U, --force-update Update to specified firmware even if version matches
or is older than installed version
--fw-version version Use a specific firmware version for update or
autoinstall
--fw-url url Use an alternate firmware download URL
--nocheck Don't check for firmware updates online
-e, --extract Extract firmware from connected RNode for later use
-E, --use-extracted Use the extracted firmware for autoinstallation or update
-E, --use-extracted Use the extracted firmware for autoinstallation or
update
-C, --clear-cache Clear locally cached firmware files
--baud-flash baud_flash
Set specific baud rate when flashing device. Default is 921600
Set specific baud rate when flashing device. Default
is 921600
-N, --normal Switch device to normal mode
-T, --tnc Switch device to TNC mode
-b, --bluetooth-on Turn device bluetooth on
-B, --bluetooth-off Turn device bluetooth off
-p, --bluetooth-pair Put device into bluetooth pairing mode
-D i, --display i Set display intensity (0-255)
--display-addr byte Set display address as hex byte (00 - FF)
--freq Hz Frequency in Hz for TNC mode
--bw Hz Bandwidth in Hz for TNC mode
--txp dBm TX power in dBm for TNC mode
@@ -728,10 +759,46 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
-P, --public Display public part of signing key
--trust-key hexbytes Public key to trust for device verification
--version Print program version and exit
-f, --flash Flash firmware and bootstrap EEPROM
-r, --rom Bootstrap EEPROM without flashing firmware
-k, --key Generate a new signing key and exit
-S, --sign Display public part of signing key
-H FIRMWARE_HASH, --firmware-hash FIRMWARE_HASH
Display installed firmware hash
--platform platform Platform specification for device bootstrap
--product product Product specification for device bootstrap
--model model Model code for device bootstrap
--hwrev revision Hardware revision for device bootstrap
For more information on how to create your own RNodes, please read the :ref:`Creating RNodes<rnode-creating>`
section of this manual.
Remote Management
-----------------
It is possible to allow remote management of Reticulum
systems using the various built-in utilities, such as
``rnstatus`` and ``rnpath``. To do so, you will need to set
the ``enable_remote_management`` directive in the ``[reticulum]``
section of the configuration file. You will also need to specify
one or more Reticulum Identity hashes for authenticating the
queries from client programs. For this purpose, you can use
existing identity files, or generate new ones with the rnid utility.
The following is a truncated example of enabling remote management
in the Reticulum configuration file:
.. code:: text
[reticulum]
...
enable_remote_management = yes
remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
...
For a complete example configuration, you can run ``rnsd --exampleconfig``.
Improving System Configuration
------------------------------
@@ -780,6 +847,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
@@ -827,3 +897,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
+7 -4
View File
@@ -53,9 +53,9 @@ What does Reticulum Offer?
* Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
* Reticulum uses a modified version of the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
* AES-128 in CBC mode with PKCS7 padding
@@ -63,6 +63,8 @@ What does Reticulum Offer?
* IVs are generated through os.urandom()
* No Version and Timestamp metadata included
* Unforgeable packet delivery confirmations
* A variety of supported interface types
@@ -91,7 +93,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.
@@ -99,7 +101,8 @@ of the types of interfaces Reticulum was designed for.
An open-source LoRa-based interface called `RNode <https://unsigned.io/rnode>`_
has been designed as an example transceiver that is very suitable for
Reticulum. It is possible to build it yourself, to transform a common LoRa
development board into one, or it can be purchased as a complete transceiver.
development board into one, or it can be purchased as a complete transceiver
from various vendors.
Reticulum can also be encapsulated over existing IP networks, so there's
nothing stopping you from using it over wired Ethernet or your local WiFi
@@ -0,0 +1,134 @@
/*
* _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;
}
+1 -22
View File
@@ -4,7 +4,7 @@
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@@ -324,7 +324,6 @@ aside.sidebar {
p.sidebar-title {
font-weight: bold;
}
nav.contents,
aside.topic,
div.admonition, div.topic, blockquote {
@@ -332,7 +331,6 @@ div.admonition, div.topic, blockquote {
}
/* -- topics ---------------------------------------------------------------- */
nav.contents,
aside.topic,
div.topic {
@@ -608,7 +606,6 @@ ol.simple p,
ul.simple p {
margin-bottom: 0;
}
aside.footnote > span,
div.citation > span {
float: left;
@@ -670,16 +667,6 @@ 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;
@@ -748,14 +735,6 @@ 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 {
+1 -1
View File
@@ -4,7 +4,7 @@
*
* Base JavaScript utilities for all Sphinx HTML documentation.
*
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.6.2 beta',
VERSION: '0.8.4 beta',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+10881
View File
File diff suppressed because it is too large Load Diff
+2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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-2023 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
+6 -6
View File
@@ -106,17 +106,17 @@ body[data-theme="dark"] .highlight .cp { color: #ff3a3a; font-weight: bold } /*
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 .gd { color: #ff3a3a } /* 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 .gr { color: #ff3a3a } /* 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 */
body[data-theme="dark"] .highlight .go { color: #cccccc } /* Generic.Output */
body[data-theme="dark"] .highlight .gp { color: #aaaaaa } /* Generic.Prompt */
body[data-theme="dark"] .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */
body[data-theme="dark"] .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */
body[data-theme="dark"] .highlight .gt { color: #d22323 } /* Generic.Traceback */
body[data-theme="dark"] .highlight .gt { color: #ff3a3a } /* Generic.Traceback */
body[data-theme="dark"] .highlight .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */
body[data-theme="dark"] .highlight .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */
body[data-theme="dark"] .highlight .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */
@@ -192,17 +192,17 @@ body:not([data-theme="light"]) .highlight .cp { color: #ff3a3a; font-weight: bol
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 .gd { color: #ff3a3a } /* 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 .gr { color: #ff3a3a } /* 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 */
body:not([data-theme="light"]) .highlight .go { color: #cccccc } /* Generic.Output */
body:not([data-theme="light"]) .highlight .gp { color: #aaaaaa } /* Generic.Prompt */
body:not([data-theme="light"]) .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */
body:not([data-theme="light"]) .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */
body:not([data-theme="light"]) .highlight .gt { color: #d22323 } /* Generic.Traceback */
body:not([data-theme="light"]) .highlight .gt { color: #ff3a3a } /* Generic.Traceback */
body:not([data-theme="light"]) .highlight .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */
body:not([data-theme="light"]) .highlight .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */
body:not([data-theme="light"]) .highlight .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */
@@ -0,0 +1,7 @@
/*!
* gumshoejs v5.1.2 (patched by @pradyunsg)
* A simple, framework-agnostic scrollspy script.
* (c) 2019 Chris Ferdinandi
* MIT License
* http://github.com/cferdinandi/gumshoe
*/
+1 -1
View File
@@ -4,7 +4,7 @@
*
* Sphinx JavaScript utilities for the full-text search.
*
* :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+19 -17
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Code Examples - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Code Examples - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -3084,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>
@@ -3322,11 +3321,14 @@ 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?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+18 -15
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -257,11 +257,14 @@
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+57 -16
View File
@@ -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-7.1.2, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -139,7 +139,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -159,13 +159,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -281,6 +281,8 @@
<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.RequestReceipt.concluded">concluded() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Buffer.create_bidirectional_buffer">create_bidirectional_buffer() (RNS.Buffer static method)</a>
</li>
@@ -291,6 +293,8 @@
<li><a href="reference.html#RNS.Buffer.create_reader">create_reader() (RNS.Buffer static method)</a>
</li>
<li><a href="reference.html#RNS.Buffer.create_writer">create_writer() (RNS.Buffer static method)</a>
</li>
<li><a href="reference.html#RNS.Identity.current_ratchet_id">current_ratchet_id() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Identity.CURVE">CURVE (RNS.Identity attribute)</a>
@@ -328,6 +332,8 @@
<h2>E</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.enable_ratchets">enable_ratchets() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.encrypt">encrypt() (RNS.Destination method)</a>
<ul>
@@ -337,6 +343,8 @@
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet.ENCRYPTED_MDU">ENCRYPTED_MDU (RNS.Packet attribute)</a>
</li>
<li><a href="reference.html#RNS.Destination.enforce_ratchets">enforce_ratchets() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">ESTABLISHMENT_TIMEOUT_PER_HOP (RNS.Link attribute)</a>
</li>
@@ -366,6 +374,8 @@
<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_age">get_age() (RNS.Link method)</a>
</li>
<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>
@@ -373,6 +383,8 @@
<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>
@@ -392,6 +404,8 @@
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.get_q">get_q() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
@@ -401,10 +415,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>
@@ -502,6 +520,8 @@
</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>
@@ -520,6 +540,8 @@
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.no_data_for">no_data_for() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.no_inbound_for">no_inbound_for() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.no_outbound_for">no_outbound_for() (RNS.Link method)</a>
@@ -552,6 +574,14 @@
<h2>R</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.RATCHET_COUNT">RATCHET_COUNT (RNS.Destination attribute)</a>
</li>
<li><a href="reference.html#RNS.Identity.RATCHET_EXPIRY">RATCHET_EXPIRY (RNS.Identity attribute)</a>
</li>
<li><a href="reference.html#RNS.Destination.RATCHET_INTERVAL">RATCHET_INTERVAL (RNS.Destination attribute)</a>
</li>
<li><a href="reference.html#RNS.Identity.RATCHETSIZE">RATCHETSIZE (RNS.Identity attribute)</a>
</li>
<li><a href="reference.html#RNS.RawChannelReader">RawChannelReader (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.RawChannelWriter">RawChannelWriter (class in RNS)</a>
@@ -563,11 +593,13 @@
<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.Channel.Channel.register_message_type">register_message_type() (RNS.Channel.Channel method)</a>
</li>
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.remote_management_enabled">remote_management_enabled() (RNS.Reticulum static method)</a>
</li>
<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>
@@ -615,6 +647,8 @@
<li><a href="reference.html#RNS.Destination.set_proof_requested_callback">set_proof_requested_callback() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_proof_strategy">set_proof_strategy() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_ratchet_interval">set_ratchet_interval() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_remote_identified_callback">set_remote_identified_callback() (RNS.Link method)</a>
</li>
@@ -627,6 +661,8 @@
<li><a href="reference.html#RNS.Link.set_resource_started_callback">set_resource_started_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_resource_strategy">set_resource_strategy() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_retained_ratchets">set_retained_ratchets() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_timeout">set_timeout() (RNS.PacketReceipt method)</a>
</li>
@@ -656,10 +692,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>
@@ -723,11 +761,14 @@
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+40 -20
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Getting Started Fast - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Getting Started Fast - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -278,7 +278,8 @@ radio interfaces can then be added later.</p>
<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>
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
such as LoRa or packet radio.</p>
</section>
<section id="nomad-network">
<h3>Nomad Network<a class="headerlink" href="#nomad-network" title="Permalink to this heading">#</a></h3>
@@ -308,13 +309,22 @@ program, reboot your system and try again.</p>
<h3>Sideband<a class="headerlink" href="#sideband" title="Permalink to this heading">#</a></h3>
<p>If you would rather use a program with a graphical user interface, you can take
a look at <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>, which is available for Android,
Linux and macOS.</p>
Linux, macOS and Windows.</p>
<a class="reference external image-reference" href="_images/sideband_devices.webp"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" /></a>
<p>Sideband allows you to communicate with other people or LXMF-compatible
systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR
Paper Messages, or anything else Reticulum supports. It also interoperates with
the Nomad Network program.</p>
</section>
<section id="meshchat">
<h3>MeshChat<a class="headerlink" href="#meshchat" title="Permalink to this heading">#</a></h3>
<p>The <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> application
is a user-friendly LXMF client for macOS and Windows, that also includes voice call
functionality, and a range of other interesting functions.</p>
<a class="reference external image-reference" href="_images/meshchat_1.webp"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" /></a>
<p>Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.</p>
</section>
</section>
<section id="using-the-included-utilities">
<h2>Using the Included Utilities<a class="headerlink" href="#using-the-included-utilities" title="Permalink to this heading">#</a></h2>
@@ -399,7 +409,7 @@ by adding one of the following interfaces to your <code class="docutils literal
<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">betweentheborders</span><span class="o">.</span><span class="n">com</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="n">reticulum</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 Testnet I2P Hub</span>
@@ -412,7 +422,13 @@ by adding one of the following interfaces to your <code class="docutils literal
<p>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
topography, usage or what types of instances connect. It will also occasionally be used
to test various failure scenarios, and there are no availability or service guarantees.</p>
to test various failure scenarios, and there are no availability or service guarantees.
Expect weird things to happen on this network, as people experiment and try out things.</p>
<p>It probably goes without saying, but <em>dont use the testnet entry-points as
hardcoded or default interfaces in any applications you ship to users</em>. When
shipping applications, the best practice is to provide your own default
connectivity solutions, if needed and applicable, or in most cases, simply
leave it up to the user which networks to connect to, and how.</p>
</section>
<section id="adding-radio-interfaces">
<h2>Adding Radio Interfaces<a class="headerlink" href="#adding-radio-interfaces" title="Permalink to this heading">#</a></h2>
@@ -560,7 +576,7 @@ locally on your device using the following command:</p>
</div>
<p>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 <a class="reference external" href="https://github.com/markqvist/sideband">Sideband source code</a> as an example and startig point.</p>
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 starting point.</p>
</section>
<section id="arm64">
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
@@ -729,6 +745,7 @@ section of this manual.</p>
<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>
<li><a class="reference internal" href="#meshchat">MeshChat</a></li>
</ul>
</li>
<li><a class="reference internal" href="#using-the-included-utilities">Using the Included Utilities</a></li>
@@ -758,11 +775,14 @@ section of this manual.</p>
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+119 -51
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Configuring Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Communications Hardware - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Communications Hardware - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -291,65 +291,126 @@ to the configuration.</p>
<span id="rnode-supported"></span><h3>Supported Boards<a class="headerlink" href="#supported-boards" title="Permalink to this heading">#</a></h3>
<p>To create one or more RNodes, you will need to obtain supported development
boards. The following boards are supported by the auto-installer.</p>
<section id="lilygo-lora32-v2-1">
<h4>LilyGO LoRa32 v2.1<a class="headerlink" href="#lilygo-lora32-v2-1" title="Permalink to this heading">#</a></h4>
<a class="reference internal image-reference" href="_images/board_t3v21.png"><img alt="_images/board_t3v21.png" class="align-center" src="_images/board_t3v21.png" style="width: 46%;" /></a>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_tbeam_supreme.png"><img alt="_images/board_tbeam_supreme.png" class="align-center" src="_images/board_tbeam_supreme.png" style="width: 75%;" /></a>
<section id="lilygo-t-beam-supreme">
<h4>LilyGO T-Beam Supreme<a class="headerlink" href="#lilygo-t-beam-supreme" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
</section>
<section id="lilygo-lora32-v2-0">
<h4>LilyGO LoRa32 v2.0<a class="headerlink" href="#lilygo-lora32-v2-0" title="Permalink to this heading">#</a></h4>
<a class="reference internal image-reference" href="_images/board_t3v20.png"><img alt="_images/board_t3v20.png" class="align-center" src="_images/board_t3v20.png" style="width: 46%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_tbeam.png"><img alt="_images/board_tbeam.png" class="align-center" src="_images/board_tbeam.png" style="width: 75%;" /></a>
</section>
<section id="lilygo-t-beam">
<h4>LilyGO T-Beam<a class="headerlink" href="#lilygo-t-beam" title="Permalink to this heading">#</a></h4>
<a class="reference internal image-reference" href="_images/board_tbeam.png"><img alt="_images/board_tbeam.png" class="align-center" src="_images/board_tbeam.png" style="width: 75%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268, SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_t3s3.png"><img alt="_images/board_t3s3.png" class="align-center" src="_images/board_t3s3.png" style="width: 50%;" /></a>
</section>
<section id="heltec-lora32-v2-0">
<h4>Heltec LoRa32 v2.0<a class="headerlink" href="#heltec-lora32-v2-0" title="Permalink to this heading">#</a></h4>
<a class="reference internal image-reference" href="_images/board_heltec32.png"><img alt="_images/board_heltec32.png" class="align-center" src="_images/board_heltec32.png" style="width: 58%;" /></a>
<section id="lilygo-t3s3">
<h4>LilyGO T3S3<a class="headerlink" href="#lilygo-t3s3" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268, SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_rak4631.png"><img alt="_images/board_rak4631.png" class="align-center" src="_images/board_rak4631.png" style="width: 45%;" /></a>
</section>
<section id="rak4631-based-boards">
<h4>RAK4631-based Boards<a class="headerlink" href="#rak4631-based-boards" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268</p></li>
<li><p><strong>Device Platform</strong> nRF52</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://www.rakwireless.com">RAK Wireless</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_rnodev2.png"><img alt="_images/board_rnodev2.png" class="align-center" src="_images/board_rnodev2.png" style="width: 68%;" /></a>
</section>
<section id="unsigned-rnode-v2-x">
<h4>Unsigned RNode v2.x<a class="headerlink" href="#unsigned-rnode-v2-x" title="Permalink to this heading">#</a></h4>
<a class="reference internal image-reference" href="_images/board_rnodev2.png"><img alt="_images/board_rnodev2.png" class="align-center" src="_images/board_rnodev2.png" style="width: 58%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://unsigned.io">unsigned.io</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_t3v21.png"><img alt="_images/board_t3v21.png" class="align-center" src="_images/board_t3v21.png" style="width: 46%;" /></a>
</section>
<section id="lilygo-lora32-v2-1">
<h4>LilyGO LoRa32 v2.1<a class="headerlink" href="#lilygo-lora32-v2-1" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_t3v20.png"><img alt="_images/board_t3v20.png" class="align-center" src="_images/board_t3v20.png" style="width: 46%;" /></a>
</section>
<section id="lilygo-lora32-v2-0">
<h4>LilyGO LoRa32 v2.0<a class="headerlink" href="#lilygo-lora32-v2-0" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_t3v10.png"><img alt="_images/board_t3v10.png" class="align-center" src="_images/board_t3v10.png" style="width: 46%;" /></a>
</section>
<section id="lilygo-lora32-v1-0">
<h4>LilyGO LoRa32 v1.0<a class="headerlink" href="#lilygo-lora32-v1-0" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_tdeck.png"><img alt="_images/board_tdeck.png" class="align-center" src="_images/board_tdeck.png" style="width: 45%;" /></a>
</section>
<section id="lilygo-t-deck">
<h4>LilyGO T-Deck<a class="headerlink" href="#lilygo-t-deck" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_heltec32v30.png"><img alt="_images/board_heltec32v30.png" class="align-center" src="_images/board_heltec32v30.png" style="width: 58%;" /></a>
</section>
<section id="heltec-lora32-v3-0">
<h4>Heltec LoRa32 v3.0<a class="headerlink" href="#heltec-lora32-v3-0" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1262 and SX1268</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_heltec32v20.png"><img alt="_images/board_heltec32v20.png" class="align-center" src="_images/board_heltec32v20.png" style="width: 58%;" /></a>
</section>
<section id="heltec-lora32-v2-0">
<h4>Heltec LoRa32 v2.0<a class="headerlink" href="#heltec-lora32-v2-0" title="Permalink to this heading">#</a></h4>
<ul class="simple">
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
</ul>
<hr class="docutils" />
<a class="reference internal image-reference" href="_images/board_rnode.png"><img alt="_images/board_rnode.png" class="align-center" src="_images/board_rnode.png" style="width: 50%;" /></a>
</section>
<section id="unsigned-rnode-v1-x">
<h4>Unsigned RNode v1.x<a class="headerlink" href="#unsigned-rnode-v1-x" title="Permalink to this heading">#</a></h4>
<a class="reference internal image-reference" href="_images/board_rnode.png"><img alt="_images/board_rnode.png" class="align-center" src="_images/board_rnode.png" style="width: 50%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
<li><p><strong>Device Platform</strong> AVR ATmega1284p</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://unsigned.io">unsigned.io</a></p></li>
</ul>
<hr class="docutils" />
</section>
</section>
<section id="installation">
@@ -375,10 +436,8 @@ auto-install and configure your devices.</p>
<span id="rnode-usage"></span><h3>Usage with Reticulum<a class="headerlink" href="#usage-with-reticulum" title="Permalink to this heading">#</a></h3>
<p>When the devices have been installed and provisioned, you can use them with Reticulum
by adding the <a class="reference internal" href="interfaces.html#interfaces-rnode"><span class="std std-ref">relevant interface section</span></a> to the configuration
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
RNode, using the parameters stored in the RNode itself.</p>
file of Reticulum. In the configuraion you can specify all interface parameters,
such as serial port and on-air parameters.</p>
</section>
</section>
<section id="wifi-based-hardware">
@@ -492,11 +551,17 @@ can be used with Reticulum. This includes virtual software modems such as
<li><a class="reference internal" href="#rnode">RNode</a><ul>
<li><a class="reference internal" href="#creating-rnodes">Creating RNodes</a></li>
<li><a class="reference internal" href="#supported-boards">Supported Boards</a><ul>
<li><a class="reference internal" href="#lilygo-t-beam-supreme">LilyGO T-Beam Supreme</a></li>
<li><a class="reference internal" href="#lilygo-t-beam">LilyGO T-Beam</a></li>
<li><a class="reference internal" href="#lilygo-t3s3">LilyGO T3S3</a></li>
<li><a class="reference internal" href="#rak4631-based-boards">RAK4631-based Boards</a></li>
<li><a class="reference internal" href="#unsigned-rnode-v2-x">Unsigned RNode v2.x</a></li>
<li><a class="reference internal" href="#lilygo-lora32-v2-1">LilyGO LoRa32 v2.1</a></li>
<li><a class="reference internal" href="#lilygo-lora32-v2-0">LilyGO LoRa32 v2.0</a></li>
<li><a class="reference internal" href="#lilygo-t-beam">LilyGO T-Beam</a></li>
<li><a class="reference internal" href="#lilygo-lora32-v1-0">LilyGO LoRa32 v1.0</a></li>
<li><a class="reference internal" href="#lilygo-t-deck">LilyGO T-Deck</a></li>
<li><a class="reference internal" href="#heltec-lora32-v3-0">Heltec LoRa32 v3.0</a></li>
<li><a class="reference internal" href="#heltec-lora32-v2-0">Heltec LoRa32 v2.0</a></li>
<li><a class="reference internal" href="#unsigned-rnode-v2-x">Unsigned RNode v2.x</a></li>
<li><a class="reference internal" href="#unsigned-rnode-v1-x">Unsigned RNode v1.x</a></li>
</ul>
</li>
@@ -519,11 +584,14 @@ can be used with Reticulum. This includes virtual software modems such as
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+21 -15
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="#"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="#">
<div class="sidebar-sticky"><a class="sidebar-brand centered" 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.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -247,6 +247,7 @@ to participate in the development of Reticulum itself.</p>
<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>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#meshchat">MeshChat</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#using-the-included-utilities">Using the Included Utilities</a></li>
@@ -280,6 +281,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnodeconf-utility">The rnodeconf Utility</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="using.html#remote-management">Remote Management</a></li>
<li class="toctree-l2"><a class="reference internal" href="using.html#improving-system-configuration">Improving System Configuration</a><ul>
<li class="toctree-l3"><a class="reference internal" href="using.html#fixed-serial-port-names">Fixed Serial Port Names</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#reticulum-as-a-system-service">Reticulum as a System Service</a></li>
@@ -337,6 +339,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-client-interface">TCP Client Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-lora-interface">RNode LoRa Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-multi-interface">RNode Multi Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#serial-interface">Serial Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#pipe-interface">Pipe Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#kiss-interface">KISS Interface</a></li>
@@ -468,11 +471,14 @@ to participate in the development of Reticulum itself.</p>
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+167 -18
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Configuring Interfaces - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Configuring Interfaces - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -241,9 +241,20 @@ infrastructure like routers or DHCP servers, but will require at least some
sort of switching medium between peers (a wired switch, a hub, a WiFi access
point or similar), and that link-local IPv6 is enabled in your operating
system, which should be enabled by default in almost all OSes.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
<span class="c1"># specified IP address and port number.</span>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a bare-minimum setup</span>
<span class="c1"># of an Auto Interface. It will allow communica-</span>
<span class="c1"># tion with all other reachable devices on all</span>
<span class="c1"># usable physical ethernet-based devices that</span>
<span class="c1"># are available on the system.</span>
<span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># This example demonstrates an more specifically</span>
<span class="c1"># configured Auto Interface, that only uses spe-</span>
<span class="c1"># cific physical interfaces, and has a number of</span>
<span class="c1"># other configuration options set.</span>
<span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</span>
@@ -255,6 +266,12 @@ system, which should be enabled by default in almost all OSes.</p>
<span class="n">group_id</span> <span class="o">=</span> <span class="n">reticulum</span>
<span class="c1"># You can also choose the multicast address type:</span>
<span class="c1"># temporary (default, Temporary Multicast Address)</span>
<span class="c1"># or permanent (Permanent Multicast Address)</span>
<span class="n">multicast_address_type</span> <span class="o">=</span> <span class="n">permanent</span>
<span class="c1"># You can also select specifically which</span>
<span class="c1"># kernel networking devices to use.</span>
@@ -503,6 +520,23 @@ can be used, and offers full control over LoRa parameters.</p>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
<span class="c1"># It is also possible to use BLE devices</span>
<span class="c1"># instead of wired serial ports. The</span>
<span class="c1"># target RNode must be paired with the</span>
<span class="c1"># host device before connecting. BLE</span>
<span class="c1"># devices can be connected by name,</span>
<span class="c1"># BLE MAC address or by any available.</span>
<span class="c1"># Connect to specific device by name</span>
<span class="c1"># port = ble://RNode 3B87</span>
<span class="c1"># Or by BLE MAC address</span>
<span class="c1"># port = ble://F4:12:73:29:4E:89</span>
<span class="c1"># Or connect to the first available,</span>
<span class="c1"># paired device</span>
<span class="c1"># port = ble://</span>
<span class="c1"># Set frequency to 867.2 MHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">867200000</span>
@@ -552,6 +586,117 @@ can be used, and offers full control over LoRa parameters.</p>
</pre></div>
</div>
</section>
<section id="rnode-multi-interface">
<span id="interfaces-rnode-multi"></span><h2>RNode Multi Interface<a class="headerlink" href="#rnode-multi-interface" title="Permalink to this heading">#</a></h2>
<p>For RNodes that support multiple LoRa transceivers, the RNode
Multi interface can be used to configure sub-interfaces individually.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of how to add an RNode Multi interface</span>
<span class="c1"># using the RNode LoRa transceiver.</span>
<span class="p">[[</span><span class="n">RNode</span> <span class="n">Multi</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">RNodeMultiInterface</span>
<span class="c1"># Enable interface if you want to use it!</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyACM0</span>
<span class="c1"># You can configure the RNode to send</span>
<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>
<span class="c1"># A subinterface</span>
<span class="p">[[[</span><span class="n">HIGHDATARATE</span><span class="p">]]]</span>
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Set frequency to 2.4GHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">2400000000</span>
<span class="c1"># Set LoRa bandwidth to 1625 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">1625000</span>
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># The virtual port, only the manufacturer</span>
<span class="c1"># or the person who wrote the board config</span>
<span class="c1"># can tell you what it will be for which</span>
<span class="c1"># physical hardware interface</span>
<span class="n">vport</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Select spreading factor 5. Valid</span>
<span class="c1"># range is 5 through 12, with 5</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># It is possible to limit the airtime</span>
<span class="c1"># utilisation of an RNode by using the</span>
<span class="c1"># following two configuration options.</span>
<span class="c1"># The short-term limit is applied in a</span>
<span class="c1"># window of approximately 15 seconds,</span>
<span class="c1"># and the long-term limit is enforced</span>
<span class="c1"># over a rolling 60 minute window. Both</span>
<span class="c1"># options are specified in percent.</span>
<span class="c1"># airtime_limit_long = 100</span>
<span class="c1"># airtime_limit_short = 100</span>
<span class="p">[[[</span><span class="n">LOWDATARATE</span><span class="p">]]]</span>
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Set frequency to 865.6 MHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">865600000</span>
<span class="c1"># The virtual port, only the manufacturer</span>
<span class="c1"># or the person who wrote the board config</span>
<span class="c1"># can tell you what it will be for which</span>
<span class="c1"># physical hardware interface</span>
<span class="n">vport</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Select spreading factor 7. Valid</span>
<span class="c1"># range is 5 through 12, with 5</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">7</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># It is possible to limit the airtime</span>
<span class="c1"># utilisation of an RNode by using the</span>
<span class="c1"># following two configuration options.</span>
<span class="c1"># The short-term limit is applied in a</span>
<span class="c1"># window of approximately 15 seconds,</span>
<span class="c1"># and the long-term limit is enforced</span>
<span class="c1"># over a rolling 60 minute window. Both</span>
<span class="c1"># options are specified in percent.</span>
<span class="c1"># airtime_limit_long = 100</span>
<span class="c1"># airtime_limit_short = 100</span>
</pre></div>
</div>
</section>
<section id="serial-interface">
<span id="interfaces-serial"></span><h2>Serial Interface<a class="headerlink" href="#serial-interface" title="Permalink to this heading">#</a></h2>
<p>Reticulum can be used over serial ports directly, or over any device with a
@@ -1087,6 +1232,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
<li><a class="reference internal" href="#tcp-client-interface">TCP Client Interface</a></li>
<li><a class="reference internal" href="#udp-interface">UDP Interface</a></li>
<li><a class="reference internal" href="#rnode-lora-interface">RNode LoRa Interface</a></li>
<li><a class="reference internal" href="#rnode-multi-interface">RNode Multi Interface</a></li>
<li><a class="reference internal" href="#serial-interface">Serial Interface</a></li>
<li><a class="reference internal" href="#pipe-interface">Pipe Interface</a></li>
<li><a class="reference internal" href="#kiss-interface">KISS Interface</a></li>
@@ -1106,11 +1252,14 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+19 -16
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Configuring Interfaces" href="interfaces.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Building Networks - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Building Networks - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -294,7 +294,7 @@ than that.</em></p>
<li><div class="line-block">
<div class="line">Reticulum is designed to work reliably in open, trustless environments. This
means you can use it to create open-access networks, where participants can
join and leave in an free and unorganised manner. This property allows an
join and leave in a free and unorganised manner. This property allows an
entirely new, and so far, mostly unexplored class of networked applications,
where networks, and the information flow within them can form and dissolve
organically.</div>
@@ -467,11 +467,14 @@ 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?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
Binary file not shown.
+255 -30
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>API Reference - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>API Reference - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -253,7 +253,7 @@ other programs to use on demand.</p>
<dt class="sig sig-object py" id="RNS.Reticulum.MTU">
<span class="sig-name descname"><span class="pre">MTU</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">500</span></em><a class="headerlink" href="#RNS.Reticulum.MTU" title="Permalink to this definition">#</a></dt>
<dd><p>The MTU that Reticulum adheres to, and will expect other peers to
adhere to. By default, the MTU is 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.</p>
@@ -275,7 +275,22 @@ links dont overwhelm the capacity of smaller networks on slower
mediums. If an announce remains queued for an extended amount of time,
it will eventually be dropped.</p>
<p>This value will be applied by default to all created interfaces,
but it can be configured individually on a per-interface basis.</p>
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.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Reticulum.MINIMUM_BITRATE">
<span class="sig-name descname"><span class="pre">MINIMUM_BITRATE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">5</span></em><a class="headerlink" href="#RNS.Reticulum.MINIMUM_BITRATE" title="Permalink to this definition">#</a></dt>
<dd><p>Minimum bitrate required across a medium for Reticulum to be able
to successfully establish links. Currently 5 bits per second.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Reticulum.get_instance">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">get_instance</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.get_instance" title="Permalink to this definition">#</a></dt>
<dd><p>Return the currently running Reticulum instance</p>
</dd></dl>
<dl class="py method">
@@ -304,6 +319,20 @@ and pass announces over the network.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Reticulum.remote_management_enabled">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">remote_management_enabled</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.remote_management_enabled" title="Permalink to this definition">#</a></dt>
<dd><p>Returns whether remote management is enabled for the
running instance.</p>
<p>When remote management is enabled, authenticated peers
can remotely query and manage this instance.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>True if remote management is enabled, False if not.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
<p id="api-identity"><h3> Identity </h3></p>
@@ -327,7 +356,21 @@ for all encrypted communication over Reticulum networks.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.KEYSIZE">
<span class="sig-name descname"><span class="pre">KEYSIZE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">512</span></em><a class="headerlink" href="#RNS.Identity.KEYSIZE" title="Permalink to this definition">#</a></dt>
<dd><p>X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
<dd><p>X.25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.RATCHETSIZE">
<span class="sig-name descname"><span class="pre">RATCHETSIZE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">256</span></em><a class="headerlink" href="#RNS.Identity.RATCHETSIZE" title="Permalink to this definition">#</a></dt>
<dd><p>X.25519 ratchet key size in bits.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.RATCHET_EXPIRY">
<span class="sig-name descname"><span class="pre">RATCHET_EXPIRY</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2592000</span></em><a class="headerlink" href="#RNS.Identity.RATCHET_EXPIRY" title="Permalink to this definition">#</a></dt>
<dd><p>The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
announced ratchet, and remember it for up to <code class="docutils literal notranslate"><span class="pre">RATCHET_EXPIRY</span></code> since receiving it, after which it will be discarded.
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.</p>
</dd></dl>
<dl class="py attribute">
@@ -374,7 +417,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
<dd class="field-odd"><p><strong>data</strong> Data to be hashed as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>SHA-256 hash as <em>bytes</em></p>
<dd class="field-even"><p>SHA-256 hash as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -388,7 +431,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
<dd class="field-odd"><p><strong>data</strong> Data to be hashed as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>Truncated SHA-256 hash as <em>bytes</em></p>
<dd class="field-even"><p>Truncated SHA-256 hash as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -402,7 +445,21 @@ for addressable hashes and other purposes. Non-configurable.</p>
<dd class="field-odd"><p><strong>data</strong> Data to be hashed as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>Truncated SHA-256 hash of random data as <em>bytes</em></p>
<dd class="field-even"><p>Truncated SHA-256 hash of random data as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.current_ratchet_id">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">current_ratchet_id</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.current_ratchet_id" title="Permalink to this definition">#</a></dt>
<dd><p>Get the ID of the currently used ratchet key for a given destination hash</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>destination_hash</strong> A destination hash as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>A ratchet ID as <em>bytes</em> or <em>None</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -503,7 +560,7 @@ communication for the identity. Be very careful with this method.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
<dd><p>Encrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -520,7 +577,7 @@ communication for the identity. Be very careful with this method.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.decrypt">
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchets</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">enforce_ratchets</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet_id_receiver</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
<dd><p>Decrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -582,7 +639,7 @@ communication for the identity. Be very careful with this method.</p>
instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its
presence on the network, which will also distribute necessary keys for
presence on the network, which will distribute necessary keys for
encrypted communication with it.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -595,6 +652,18 @@ encrypted communication with it.</p>
</ul>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Destination.RATCHET_COUNT">
<span class="sig-name descname"><span class="pre">RATCHET_COUNT</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">512</span></em><a class="headerlink" href="#RNS.Destination.RATCHET_COUNT" title="Permalink to this definition">#</a></dt>
<dd><p>The default number of generated ratchet keys a destination will retain, if it has ratchets enabled.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Destination.RATCHET_INTERVAL">
<span class="sig-name descname"><span class="pre">RATCHET_INTERVAL</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">1800</span></em><a class="headerlink" href="#RNS.Destination.RATCHET_INTERVAL" title="Permalink to this definition">#</a></dt>
<dd><p>The minimum interval between rotating ratchet keys, in seconds.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.expand_name">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">expand_name</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">app_name</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span><span class="n"><span class="pre">aspects</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.expand_name" title="Permalink to this definition">#</a></dt>
@@ -745,6 +814,64 @@ proofs should be returned for received packets.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.enable_ratchets">
<span class="sig-name descname"><span class="pre">enable_ratchets</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ratchets_path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.enable_ratchets" title="Permalink to this definition">#</a></dt>
<dd><p>Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate
the keys used to encrypt packets to this destination, and include the latest ratchet key in announces.</p>
<p>Enabling ratchets on a destination will provide forward secrecy for packets sent to that destination,
even when sent outside a <code class="docutils literal notranslate"><span class="pre">Link</span></code>. The normal Reticulum <code class="docutils literal notranslate"><span class="pre">Link</span></code> establishment procedure already performs
its own ephemeral key exchange for each link establishment, which means that ratchets are not necessary
to provide forward secrecy for links.</p>
<p>Enabling ratchets will have a small impact on announce size, adding 32 bytes to every sent announce.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>ratchets_path</strong> The path to a file to store ratchet data in.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>True if the operation succeeded, otherwise False.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.enforce_ratchets">
<span class="sig-name descname"><span class="pre">enforce_ratchets</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.enforce_ratchets" title="Permalink to this definition">#</a></dt>
<dd><p>When ratchet enforcement is enabled, this destination will never accept packets that use its
base Identity key for encryption, but only accept packets encrypted with one of the retained
ratchet keys.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.set_retained_ratchets">
<span class="sig-name descname"><span class="pre">set_retained_ratchets</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">retained_ratchets</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_retained_ratchets" title="Permalink to this definition">#</a></dt>
<dd><p>Sets the number of previously generated ratchet keys this destination will retain,
and try to use when decrypting incoming packets. Defaults to <code class="docutils literal notranslate"><span class="pre">Destination.RATCHET_COUNT</span></code>.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>retained_ratchets</strong> The number of generated ratchets to retain.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>True if the operation succeeded, False if not.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.set_ratchet_interval">
<span class="sig-name descname"><span class="pre">set_ratchet_interval</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">interval</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_ratchet_interval" title="Permalink to this definition">#</a></dt>
<dd><p>Sets the minimum interval in seconds between ratchet key rotation.
Defaults to <code class="docutils literal notranslate"><span class="pre">Destination.RATCHET_INTERVAL</span></code>.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>interval</strong> The minimum interval in seconds.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>True if the operation succeeded, False if not.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.create_keys">
<span class="sig-name descname"><span class="pre">create_keys</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.create_keys" title="Permalink to this definition">#</a></dt>
@@ -1057,6 +1184,50 @@ thus preserved. This method can be used for authentication.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.track_phy_stats">
<span class="sig-name descname"><span class="pre">track_phy_stats</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">track</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.track_phy_stats" title="Permalink to this definition">#</a></dt>
<dd><p>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 <em>RSSI</em>, <em>SNR</em> and physical
<em>Link Quality</em> for the link.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>track</strong> Whether or not to keep track of physical layer statistics. Value must be <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">False</span></code>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_rssi">
<span class="sig-name descname"><span class="pre">get_rssi</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_rssi" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The physical layer <em>Received Signal Strength Indication</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>. Physical layer statistics must be enabled on the link for this method to return a value.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_snr">
<span class="sig-name descname"><span class="pre">get_snr</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_snr" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The physical layer <em>Signal-to-Noise Ratio</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>. Physical layer statistics must be enabled on the link for this method to return a value.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_q">
<span class="sig-name descname"><span class="pre">get_q</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_q" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The physical layer <em>Link Quality</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>. Physical layer statistics must be enabled on the link for this method to return a value.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_establishment_rate">
<span class="sig-name descname"><span class="pre">get_establishment_rate</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_establishment_rate" title="Permalink to this definition">#</a></dt>
@@ -1067,12 +1238,22 @@ thus preserved. This method can be used for authentication.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_age">
<span class="sig-name descname"><span class="pre">get_age</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_age" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The time in seconds since this link was established.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.no_inbound_for">
<span class="sig-name descname"><span class="pre">no_inbound_for</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.no_inbound_for" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The time in seconds since last inbound packet on the link.</p>
<dd class="field-odd"><p>The time in seconds since last inbound packet on the link. This includes keepalive packets.</p>
</dd>
</dl>
</dd></dl>
@@ -1082,7 +1263,17 @@ thus preserved. This method can be used for authentication.</p>
<span class="sig-name descname"><span class="pre">no_outbound_for</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.no_outbound_for" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The time in seconds since last outbound packet on the link.</p>
<dd class="field-odd"><p>The time in seconds since last outbound packet on the link. This includes keepalive packets.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.no_data_for">
<span class="sig-name descname"><span class="pre">no_data_for</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.no_data_for" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The time in seconds since payload data traversed the link. This excludes keepalive packets.</p>
</dd>
</dl>
</dd></dl>
@@ -1092,7 +1283,7 @@ thus preserved. This method can be used for authentication.</p>
<span class="sig-name descname"><span class="pre">inactive_for</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.inactive_for" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The time in seconds since activity on the link.</p>
<dd class="field-odd"><p>The time in seconds since activity on the link. This includes keepalive packets.</p>
</dd>
</dl>
</dd></dl>
@@ -1272,6 +1463,16 @@ check status, response time and response data when the request concludes.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.concluded">
<span class="sig-name descname"><span class="pre">concluded</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.concluded" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>True if the associated request has concluded (successfully or with a failure), otherwise False.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
<p id="api-resource"><h3> Resource </h3></p>
@@ -1536,7 +1737,7 @@ and <code class="docutils literal notranslate"><span class="pre">BufferedRWPair<
<code class="docutils literal notranslate"><span class="pre">RawChannelReader</span></code> and <code class="docutils literal notranslate"><span class="pre">RawChannelWriter</span></code>.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Buffer.create_reader">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_reader</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ready_callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">BufferedReader</span></span></span><a class="headerlink" href="#RNS.Buffer.create_reader" title="Permalink to this definition">#</a></dt>
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_reader</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ready_callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">BufferedReader</span></span></span><a class="headerlink" href="#RNS.Buffer.create_reader" title="Permalink to this definition">#</a></dt>
<dd><p>Create a buffered reader that reads binary data sent
over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>, with an optional callback when
new data is available.</p>
@@ -1581,7 +1782,7 @@ of this object, see the Python documentation for
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Buffer.create_bidirectional_buffer">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_bidirectional_buffer</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">receive_stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">send_stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ready_callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span><span class="w"> </span><span class="p"><span class="pre">|</span></span><span class="w"> </span><span class="pre">None</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">BufferedRWPair</span></span></span><a class="headerlink" href="#RNS.Buffer.create_bidirectional_buffer" title="Permalink to this definition">#</a></dt>
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_bidirectional_buffer</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">receive_stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">send_stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ready_callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">BufferedRWPair</span></span></span><a class="headerlink" href="#RNS.Buffer.create_bidirectional_buffer" title="Permalink to this definition">#</a></dt>
<dd><p>Create a buffered reader/writer pair that reads and
writes binary data over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>, with an
optional callback when new data is available.</p>
@@ -1711,7 +1912,9 @@ Transport system of Reticulum.</p>
<dd><p>Registers an announce handler.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>handler</strong> Must be an object with an <em>aspect_filter</em> attribute and a <em>received_announce(destination_hash, announced_identity, app_data)</em> callable. See the <a class="reference internal" href="examples.html#example-announce"><span class="std std-ref">Announce Example</span></a> for more info.</p>
<dd class="field-odd"><p><strong>handler</strong> Must be an object with an <em>aspect_filter</em> attribute and a <em>received_announce(destination_hash, announced_identity, app_data)</em>
callable. Can optionally have a <em>receive_path_responses</em> attribute set to <code class="docutils literal notranslate"><span class="pre">True</span></code>, to also receive all path responses, in addition to live
announces. See the <a class="reference internal" href="examples.html#example-announce"><span class="std std-ref">Announce Example</span></a> for more info.</p>
</dd>
</dl>
</dd></dl>
@@ -1851,19 +2054,25 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Reticulum"><code class="docutils literal notranslate"><span class="pre">Reticulum</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Reticulum.MTU"><code class="docutils literal notranslate"><span class="pre">MTU</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.ANNOUNCE_CAP"><code class="docutils literal notranslate"><span class="pre">ANNOUNCE_CAP</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.MINIMUM_BITRATE"><code class="docutils literal notranslate"><span class="pre">MINIMUM_BITRATE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.get_instance"><code class="docutils literal notranslate"><span class="pre">get_instance()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.should_use_implicit_proof"><code class="docutils literal notranslate"><span class="pre">should_use_implicit_proof()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.transport_enabled"><code class="docutils literal notranslate"><span class="pre">transport_enabled()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.remote_management_enabled"><code class="docutils literal notranslate"><span class="pre">remote_management_enabled()</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Identity"><code class="docutils literal notranslate"><span class="pre">Identity</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Identity.CURVE"><code class="docutils literal notranslate"><span class="pre">CURVE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.KEYSIZE"><code class="docutils literal notranslate"><span class="pre">KEYSIZE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.RATCHETSIZE"><code class="docutils literal notranslate"><span class="pre">RATCHETSIZE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.RATCHET_EXPIRY"><code class="docutils literal notranslate"><span class="pre">RATCHET_EXPIRY</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.TRUNCATED_HASHLENGTH"><code class="docutils literal notranslate"><span class="pre">TRUNCATED_HASHLENGTH</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.recall"><code class="docutils literal notranslate"><span class="pre">recall()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.recall_app_data"><code class="docutils literal notranslate"><span class="pre">recall_app_data()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.full_hash"><code class="docutils literal notranslate"><span class="pre">full_hash()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.truncated_hash"><code class="docutils literal notranslate"><span class="pre">truncated_hash()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.get_random_hash"><code class="docutils literal notranslate"><span class="pre">get_random_hash()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.current_ratchet_id"><code class="docutils literal notranslate"><span class="pre">current_ratchet_id()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.from_bytes"><code class="docutils literal notranslate"><span class="pre">from_bytes()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.from_file"><code class="docutils literal notranslate"><span class="pre">from_file()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.to_file"><code class="docutils literal notranslate"><span class="pre">to_file()</span></code></a></li>
@@ -1878,6 +2087,8 @@ will announce it.</p>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Destination"><code class="docutils literal notranslate"><span class="pre">Destination</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Destination.RATCHET_COUNT"><code class="docutils literal notranslate"><span class="pre">RATCHET_COUNT</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.RATCHET_INTERVAL"><code class="docutils literal notranslate"><span class="pre">RATCHET_INTERVAL</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.expand_name"><code class="docutils literal notranslate"><span class="pre">expand_name()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.app_and_aspects_from_name"><code class="docutils literal notranslate"><span class="pre">app_and_aspects_from_name()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.hash_from_name_and_identity"><code class="docutils literal notranslate"><span class="pre">hash_from_name_and_identity()</span></code></a></li>
@@ -1890,6 +2101,10 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Destination.set_proof_strategy"><code class="docutils literal notranslate"><span class="pre">set_proof_strategy()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.register_request_handler"><code class="docutils literal notranslate"><span class="pre">register_request_handler()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.deregister_request_handler"><code class="docutils literal notranslate"><span class="pre">deregister_request_handler()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.enable_ratchets"><code class="docutils literal notranslate"><span class="pre">enable_ratchets()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.enforce_ratchets"><code class="docutils literal notranslate"><span class="pre">enforce_ratchets()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.set_retained_ratchets"><code class="docutils literal notranslate"><span class="pre">set_retained_ratchets()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.set_ratchet_interval"><code class="docutils literal notranslate"><span class="pre">set_ratchet_interval()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.create_keys"><code class="docutils literal notranslate"><span class="pre">create_keys()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.get_private_key"><code class="docutils literal notranslate"><span class="pre">get_private_key()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.load_private_key"><code class="docutils literal notranslate"><span class="pre">load_private_key()</span></code></a></li>
@@ -1924,9 +2139,15 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Link.STALE_TIME"><code class="docutils literal notranslate"><span class="pre">STALE_TIME</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.identify"><code class="docutils literal notranslate"><span class="pre">identify()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.request"><code class="docutils literal notranslate"><span class="pre">request()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.track_phy_stats"><code class="docutils literal notranslate"><span class="pre">track_phy_stats()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_rssi"><code class="docutils literal notranslate"><span class="pre">get_rssi()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_snr"><code class="docutils literal notranslate"><span class="pre">get_snr()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_q"><code class="docutils literal notranslate"><span class="pre">get_q()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_establishment_rate"><code class="docutils literal notranslate"><span class="pre">get_establishment_rate()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_age"><code class="docutils literal notranslate"><span class="pre">get_age()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.no_inbound_for"><code class="docutils literal notranslate"><span class="pre">no_inbound_for()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.no_outbound_for"><code class="docutils literal notranslate"><span class="pre">no_outbound_for()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.no_data_for"><code class="docutils literal notranslate"><span class="pre">no_data_for()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.inactive_for"><code class="docutils literal notranslate"><span class="pre">inactive_for()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_remote_identity"><code class="docutils literal notranslate"><span class="pre">get_remote_identity()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.teardown"><code class="docutils literal notranslate"><span class="pre">teardown()</span></code></a></li>
@@ -1946,6 +2167,7 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.RequestReceipt.get_progress"><code class="docutils literal notranslate"><span class="pre">get_progress()</span></code></a></li>
<li><a class="reference internal" href="#RNS.RequestReceipt.get_response"><code class="docutils literal notranslate"><span class="pre">get_response()</span></code></a></li>
<li><a class="reference internal" href="#RNS.RequestReceipt.get_response_time"><code class="docutils literal notranslate"><span class="pre">get_response_time()</span></code></a></li>
<li><a class="reference internal" href="#RNS.RequestReceipt.concluded"><code class="docutils literal notranslate"><span class="pre">concluded()</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Resource"><code class="docutils literal notranslate"><span class="pre">Resource</span></code></a><ul>
@@ -2013,11 +2235,14 @@ will announce it.</p>
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+15 -12
View File
@@ -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-7.1.2, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.6.2 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.8.4 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -138,7 +138,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -158,13 +158,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -262,12 +262,15 @@
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</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>
<script src="_static/searchtools.js"></script>
<script src="_static/language_data.js"></script>
File diff suppressed because one or more lines are too long
+18 -15
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Support Reticulum - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Support Reticulum - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -330,11 +330,14 @@ 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?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+38 -28
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Understanding Reticulum - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Understanding Reticulum - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 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">
@@ -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>
@@ -360,10 +360,11 @@ be sufficient, even far into the future.</p>
<p>By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
channel to a destination, called a <em>Link</em>. Both data sent over Links and single packets offer
<em>Initiator Anonymity</em>, and links additionally offer <em>Forward Secrecy</em> by using an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
coordination, verification and reliability layers are fully autonomous and also based on elliptic
curve cryptography.</p>
<em>Initiator Anonymity</em>. Links additionally offer <em>Forward Secrecy</em> by default, employing an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
layers are fully autonomous and also based on elliptic curve cryptography.</p>
<p>Reticulum also offers symmetric key encryption for group-oriented communications, as well as
unencrypted packets for local broadcast purposes.</p>
<p>Reticulum can connect to a variety of interfaces such as radio modems, data radios and serial ports,
@@ -639,7 +640,7 @@ expect. Reticulum offers two ways to do this.</p>
<li><div class="line-block">
<div class="line">A packet is always created with an associated destination and some payload data. When the packet is sent
to a <em>single</em> destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the destinations public key, and encrypt the information.</div>
an ECDH key exchange with the destinations public key (or ratchet key, if available), and encrypt the information.</div>
</div>
</li>
<li><div class="line-block">
@@ -831,7 +832,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
@@ -927,7 +928,8 @@ A Reticulum packet is composed of the following fields:
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
[Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
@@ -959,12 +961,16 @@ type 1 0 Two byte header, one 16 byte address field
type 2 1 Two byte header, two 16 byte address fields
Context Flag
-----------------
unset 0 The context flag is used for various types
set 1 of signalling, depending on packet context
Propagation Types
-----------------
broadcast 00
transport 01
reserved 10
reserved 11
broadcast 0
transport 1
Destination Types
@@ -1063,10 +1069,11 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
<li><p>Ed25519 for signatures</p></li>
<li><p>X25519 for ECDH key exchanges</p></li>
<li><p>HKDF for key derivation</p></li>
<li><p>Fernet for encrypted tokens</p>
<li><p>Modified Fernet for encrypted tokens</p>
<ul>
<li><p>AES-128 in CBC mode</p></li>
<li><p>HMAC for message authentication</p></li>
<li><p>No Version and Timestamp metadata included</p></li>
</ul>
</li>
<li><p>SHA-256</p></li>
@@ -1196,11 +1203,14 @@ those risks are acceptable to you.</p>
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+152 -43
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -492,8 +492,9 @@ Reticulum Transport Instance &lt;5245a8efe1788c6a1cd36144a270e13b&gt; running
</pre></div>
</div>
<p><strong>All Command-Line Options</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
[-r] [-j] [-v] [filter]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-A]
[-l] [-s SORT] [-r] [-j] [-R hash] [-i path]
[-w seconds] [-v] [filter]
Reticulum Network Stack Status
@@ -506,9 +507,13 @@ options:
--version show program&#39;s version number and exit
-a, --all show all interfaces
-A, --announce-stats show announce stats
-l, --link-stats show link stats
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
-r, --reverse reverse sorting
-j, --json output in JSON format
-R hash transport identity hash of remote instance to get status from
-i path path to identity used for remote management
-w seconds timeout before giving up on remote queries
-v, --verbose
</pre></div>
</div>
@@ -609,8 +614,9 @@ Path found, destination &lt;c89b4da064bf66d280f0e4d8abfd9806&gt; is 4 hops away
</pre></div>
</div>
<p><strong>All Command-Line Options</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
[-x] [-w seconds] [-v] [destination]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-m hops]
[-r] [-d] [-D] [-x] [-w seconds] [-R hash] [-i path]
[-W seconds] [-j] [-v] [destination]
Reticulum Path Discovery Utility
@@ -622,11 +628,16 @@ options:
--config CONFIG path to alternative Reticulum config directory
--version show program&#39;s version number and exit
-t, --table show all known paths
-m hops, --max hops maximum hops to filter path table by
-r, --rates show announce rate info
-d, --drop remove the path to a destination
-D, --drop-announces drop all queued announces
-x, --drop-via drop all paths via specified transport instance
-w seconds timeout before giving up
-R hash transport identity hash of remote instance to manage
-i path path to identity used for remote management
-W seconds timeout before giving up on remote queries
-j, --json output in JSON format
-v, --verbose
</pre></div>
</div>
@@ -669,20 +680,27 @@ Round-trip time is 1.809 seconds over 1 hop [RSSI -73 dBm] [SNR 12.0 dB]
</pre></div>
</div>
<p><strong>All Command-Line Options</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [-s SIZE] [-n PROBES]
[-t seconds] [-w seconds] [--version] [-v]
[full_name] [destination_hash]
Reticulum Probe Utility
positional arguments:
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
options:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-s SIZE, --size SIZE size of probe packet payload in bytes
--version show program&#39;s version number and exit
-n PROBES, --probes PROBES
number of probes to send
-t seconds, --timeout seconds
timeout before giving up
-w seconds, --wait seconds
time between each probe
--version show program&#39;s version number and exit
-v, --verbose
</pre></div>
</div>
@@ -710,8 +728,9 @@ and simply running the program in listener mode:</p>
</pre></div>
</div>
<p><strong>All Command-Line Options</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp [-h] [--config path] [-v] [-q] [-S] [-l] [-F] [-f]
[-j path] [-b seconds] [-a allowed_hash] [-n] [-p]
[-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
@@ -726,10 +745,12 @@ options:
-q, --quiet decrease verbosity
-S, --silent disable transfer progress output
-l, --listen listen for incoming transfer requests
-F, --allow-fetch allow authenticated clients to fetch files
-f, --fetch fetch file from remote listener instead of sending
-j path, --jail path restrict fetch requests to specified path
-b seconds announce interval, 0 to only announce at startup
-a allowed_hash accept from this identity
-n, --no-auth accept files and fetches from anyone
-a allowed_hash allow this identity
-n, --no-auth accept requests from anyone
-p, --print-identity print identity and destination info and exit
-w seconds sender timeout before giving up
--version show program&#39;s version number and exit
@@ -800,15 +821,19 @@ optional arguments:
<p>The <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> utility allows you to inspect and configure existing <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNodes</span></a>, and
to create and provision new <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNodes</span></a> from any supported hardware devices.</p>
<p><strong>All Command-Line Options</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
[--trust-key hexbytes] [--version] [port]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version]
[--fw-url url] [--nocheck] [-e] [-E] [-C]
[--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
[--display-addr byte] [--freq Hz] [--bw Hz] [--txp dBm]
[--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump]
[--eeprom-wipe] [-P] [--trust-key hexbytes] [--version] [-f]
[-r] [-k] [-S] [-H FIRMWARE_HASH] [--platform platform]
[--product product] [--model model] [--hwrev revision]
[port]
RNode Configuration and firmware utility. This program allows you to change various
settings and startup modes of RNode. It can also install, flash and update the firmware
on supported devices.
RNode Configuration and firmware utility. This program allows you to change
various settings and startup modes of RNode. It can also install, flash and
update the firmware on supported devices.
positional arguments:
port serial port where RNode is attached
@@ -818,20 +843,26 @@ options:
-i, --info Show device info
-a, --autoinstall Automatic installation on various supported devices
-u, --update Update firmware to the latest version
-U, --force-update Update to specified firmware even if version matches or is older than installed version
--fw-version version Use a specific firmware version for update or autoinstall
-U, --force-update Update to specified firmware even if version matches
or is older than installed version
--fw-version version Use a specific firmware version for update or
autoinstall
--fw-url url Use an alternate firmware download URL
--nocheck Don&#39;t check for firmware updates online
-e, --extract Extract firmware from connected RNode for later use
-E, --use-extracted Use the extracted firmware for autoinstallation or update
-E, --use-extracted Use the extracted firmware for autoinstallation or
update
-C, --clear-cache Clear locally cached firmware files
--baud-flash baud_flash
Set specific baud rate when flashing device. Default is 921600
Set specific baud rate when flashing device. Default
is 921600
-N, --normal Switch device to normal mode
-T, --tnc Switch device to TNC mode
-b, --bluetooth-on Turn device bluetooth on
-B, --bluetooth-off Turn device bluetooth off
-p, --bluetooth-pair Put device into bluetooth pairing mode
-D i, --display i Set display intensity (0-255)
--display-addr byte Set display address as hex byte (00 - FF)
--freq Hz Frequency in Hz for TNC mode
--bw Hz Bandwidth in Hz for TNC mode
--txp dBm TX power in dBm for TNC mode
@@ -843,12 +874,43 @@ options:
-P, --public Display public part of signing key
--trust-key hexbytes Public key to trust for device verification
--version Print program version and exit
-f, --flash Flash firmware and bootstrap EEPROM
-r, --rom Bootstrap EEPROM without flashing firmware
-k, --key Generate a new signing key and exit
-S, --sign Display public part of signing key
-H FIRMWARE_HASH, --firmware-hash FIRMWARE_HASH
Display installed firmware hash
--platform platform Platform specification for device bootstrap
--product product Product specification for device bootstrap
--model model Model code for device bootstrap
--hwrev revision Hardware revision for device bootstrap
</pre></div>
</div>
<p>For more information on how to create your own RNodes, please read the <a class="reference internal" href="hardware.html#rnode-creating"><span class="std std-ref">Creating RNodes</span></a>
section of this manual.</p>
</section>
</section>
<section id="remote-management">
<h2>Remote Management<a class="headerlink" href="#remote-management" title="Permalink to this heading">#</a></h2>
<p>It is possible to allow remote management of Reticulum
systems using the various built-in utilities, such as
<code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> and <code class="docutils literal notranslate"><span class="pre">rnpath</span></code>. To do so, you will need to set
the <code class="docutils literal notranslate"><span class="pre">enable_remote_management</span></code> directive in the <code class="docutils literal notranslate"><span class="pre">[reticulum]</span></code>
section of the configuration file. You will also need to specify
one or more Reticulum Identity hashes for authenticating the
queries from client programs. For this purpose, you can use
existing identity files, or generate new ones with the rnid utility.</p>
<p>The following is a truncated example of enabling remote management
in the Reticulum configuration file:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[reticulum]
...
enable_remote_management = yes
remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
...
</pre></div>
</div>
<p>For a complete example configuration, you can run <code class="docutils literal notranslate"><span class="pre">rnsd</span> <span class="pre">--exampleconfig</span></code>.</p>
</section>
<section id="improving-system-configuration">
<h2>Improving System Configuration<a class="headerlink" href="#improving-system-configuration" title="Permalink to this heading">#</a></h2>
<p>If you are setting up a system for permanent use with Reticulum, there is a
@@ -886,6 +948,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
@@ -926,6 +990,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>
@@ -998,9 +1099,14 @@ WantedBy=multi-user.target
<li><a class="reference internal" href="#the-rnodeconf-utility">The rnodeconf Utility</a></li>
</ul>
</li>
<li><a class="reference internal" href="#remote-management">Remote Management</a></li>
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
<li><a class="reference internal" href="#fixed-serial-port-names">Fixed Serial Port Names</a></li>
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a></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>
@@ -1014,11 +1120,14 @@ WantedBy=multi-user.target
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+24 -19
View File
@@ -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.19: https://docutils.sourceforge.io/" />
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.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="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
<meta name="generator" content="sphinx-7.1.2, furo 2022.09.29.dev1"/>
<title>What is Reticulum? - Reticulum Network Stack 0.6.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=a746c00c" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>What is Reticulum? - Reticulum Network Stack 0.8.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.6.2 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -161,13 +161,13 @@
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.6.2 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -262,12 +262,13 @@ considered complete and stable at the moment, but could change if absolutely war
<li><p>Complete initiator anonymity, communicate without revealing your identity</p></li>
<li><p>Asymmetric encryption based on X25519, and Ed25519 signatures as a basis for all communication</p></li>
<li><p>Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519</p></li>
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
<li><p>Reticulum uses a modified version of the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
<ul>
<li><p>All keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
<li><p>Keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for authentication</p></li>
<li><p>IVs are generated through os.urandom()</p></li>
<li><p>No Version and Timestamp metadata included</p></li>
</ul>
</li>
<li><p>Unforgeable packet delivery confirmations</p></li>
@@ -293,14 +294,15 @@ considered complete and stable at the moment, but could change if absolutely war
<section id="where-can-reticulum-be-used">
<h2>Where can Reticulum be Used?<a class="headerlink" href="#where-can-reticulum-be-used" title="Permalink to this heading">#</a></h2>
<p>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.</p>
<p>An open-source LoRa-based interface called <a class="reference external" href="https://unsigned.io/rnode">RNode</a>
has been designed as an example transceiver that is very suitable for
Reticulum. It is possible to build it yourself, to transform a common LoRa
development board into one, or it can be purchased as a complete transceiver.</p>
development board into one, or it can be purchased as a complete transceiver
from various vendors.</p>
<p>Reticulum can also be encapsulated over existing IP networks, so theres
nothing stopping you from using it over wired Ethernet or your local WiFi
network, where itll work just as well. In fact, one of the strengths of
@@ -434,11 +436,14 @@ want to help out with this, or can help sponsor an audit, please do get in touch
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js?v=2c9eb04b"></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>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>
+44 -14
View File
@@ -71,7 +71,8 @@ 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.
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
such as LoRa or packet radio.
Nomad Network
^^^^^^^^^^^^^
@@ -109,7 +110,7 @@ Sideband
If you would rather use a program with a graphical user interface, you can take
a look at `Sideband <https://unsigned.io/sideband>`_, which is available for Android,
Linux and macOS.
Linux, macOS and Windows.
.. only:: html
@@ -128,6 +129,28 @@ systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted Q
Paper Messages, or anything else Reticulum supports. It also interoperates with
the Nomad Network program.
MeshChat
^^^^^^^^
The `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ application
is a user-friendly LXMF client for macOS and Windows, that also includes voice call
functionality, and a range of other interesting functions.
.. only:: html
.. image:: screenshots/meshchat_1.webp
:align: center
:target: _images/meshchat_1.webp
.. only:: latexpdf
.. image:: screenshots/meshchat_1.png
:align: center
:target: _images/meshchat_1.png
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.
Using the Included Utilities
=============================================
Reticulum comes with a range of included utilities that make it easier to
@@ -180,29 +203,29 @@ and :ref:`Interfaces<interfaces-main>` chapters of this manual.
Connecting Reticulum Instances Over the Internet
================================================
Reticulum currently offers two interfaces suitable for connecting instances over the Internet: :ref:`TCP<interfaces-tcps>`
and :ref:`I2P<interfaces-i2p>`. Each interface offers a different set of features, and Reticulum
users should carefully choose the interface which best suites their needs.
and :ref:`I2P<interfaces-i2p>`. Each interface offers a different set of features, and Reticulum
users should carefully choose the interface which best suites their needs.
The ``TCPServerInterface`` allows users to host an instance accessible over TCP/IP. This
method is generally faster, lower latency, and more energy efficient than using ``I2PInterface``,
however it also leaks more data about the server host.
TCP connections reveal the IP address of both your instance and the server to anyone who can
inspect the connection. Someone could use this information to determine your location or identity. Adversaries
inspect the connection. Someone could use this information to determine your location or identity. Adversaries
inspecting your packets may be able to record packet metadata like time of transmission and packet size.
Even though Reticulum encrypts traffic, TCP does not, so an adversary may be able to use
packet inspection to learn that a system is running Reticulum, and what other IP addresses connect to it.
Hosting a publicly reachable instance over TCP also requires a publicly reachable IP address,
which most Internet connections don't offer anymore.
The ``I2PInterface`` routes messages through the `Invisible Internet Protocol
The ``I2PInterface`` routes messages through the `Invisible Internet Protocol
(I2P) <https://geti2p.net/en/>`_. To use this interface, users must also run an I2P daemon in
parallel to ``rnsd``. For always-on I2P nodes it is recommended to use `i2pd <https://i2pd.website/>`_.
parallel to ``rnsd``. For always-on I2P nodes it is recommended to use `i2pd <https://i2pd.website/>`_.
By default, I2P will encrypt and mix all traffic sent over the Internet, and
hide both the sender and receiver Reticulum instance IP addresses. Running an I2P node
By default, I2P will encrypt and mix all traffic sent over the Internet, and
hide both the sender and receiver Reticulum instance IP addresses. Running an I2P node
will also relay other I2P user's encrypted packets, which will use extra
bandwidth and compute power, but also makes timing attacks and other forms of
bandwidth and compute power, but also makes timing attacks and other forms of
deep-packet-inspection much more difficult.
I2P also allows users to host globally available Reticulum instances from non-public IP's and behind firewalls and NAT.
@@ -231,7 +254,7 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
[[RNS Testnet BetweenTheBorders]]
type = TCPClientInterface
enabled = yes
target_host = betweentheborders.com
target_host = reticulum.betweentheborders.com
target_port = 4242
# Interface to Testnet I2P Hub
@@ -244,6 +267,13 @@ Many other Reticulum instances are connecting to this testnet, and you can also
via other entry points if you know them. There is absolutely no control over the network
topography, usage or what types of instances connect. It will also occasionally be used
to test various failure scenarios, and there are no availability or service guarantees.
Expect weird things to happen on this network, as people experiment and try out things.
It probably goes without saying, but *don't use the testnet entry-points as
hardcoded or default interfaces in any applications you ship to users*. When
shipping applications, the best practice is to provide your own default
connectivity solutions, if needed and applicable, or in most cases, simply
leave it up to the user which networks to connect to, and how.
Adding Radio Interfaces
@@ -415,7 +445,7 @@ locally on your device using the following command:
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.
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and starting point.
ARM64
@@ -455,7 +485,7 @@ for including and using Reticulum in your own scripts and programs.
.. code::
# Install pipx
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
@@ -489,7 +519,7 @@ for including and using Reticulum in your own scripts and programs.
.. code::
# Install pipx
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Some files were not shown because too many files have changed in this diff Show More