Compare commits

...

346 Commits

Author SHA1 Message Date
Mark Qvist 0ff51fed44 Updated docs 2025-02-17 22:11:43 +01:00
Mark Qvist 6e25f96024 Updated changelog 2025-02-17 22:11:38 +01:00
Mark Qvist ad228fb3b3 Copy list structures on persist 2025-02-17 19:29:23 +01:00
Mark Qvist a61b20a066 Updated version 2025-02-17 18:21:58 +01:00
Mark Qvist a49b04af21 Added missing check for path announce emission timestamp in lower hop-count announce processing. Closes #717. 2025-02-17 18:21:38 +01:00
Mark Qvist f030cf6f22 Fixed potential daemon thread IO buffer deadlock on externally mediated shutdown signal 2025-02-09 17:52:49 +01:00
Mark Qvist 9e7641d2d3 Trace application-originated exception frames on LocalInterface 2025-01-27 12:29:28 +01:00
Mark Qvist c909871fb7 Updated issue template 2025-01-27 10:25:46 +01:00
Mark Qvist 47f60b0320 Fixed missing rx/tx stat assignment 2025-01-26 01:13:58 +01:00
Mark Qvist 6797909d90 Updated logging 2025-01-24 21:04:48 +01:00
Mark Qvist fd6d8ffff8 Updated logging 2025-01-21 23:55:49 +01:00
Mark Qvist 06de7f4a3d Updated logging 2025-01-21 23:22:39 +01:00
Mark Qvist 7221becd35 Updated manual 2025-01-19 21:09:50 +01:00
Mark Qvist a51f5f2eaf Updated changelog 2025-01-19 21:09:40 +01:00
Mark Qvist 9e8d71ddaf Updated examples 2025-01-19 20:38:41 +01:00
Mark Qvist 9bc55a9047 Updated manual 2025-01-19 12:56:32 +01:00
Mark Qvist 3e7ab5136e Better thread configuration 2025-01-19 00:57:36 +01:00
Mark Qvist d2cf3c2a7e Added error on configured radio parameter mismatch on Android 2025-01-19 00:10:35 +01:00
Mark Qvist 77519f1a0c Updated version 2025-01-18 21:38:13 +01:00
Mark Qvist e869b3cac9 Added resource reject signalling 2025-01-18 21:37:17 +01:00
Mark Qvist a2878f1722 Cleanup 2025-01-17 12:48:10 +01:00
Mark Qvist 748a7290a9 Updated docs 2025-01-17 12:47:02 +01:00
Mark Qvist 6e80a553c8 Updated changelog 2025-01-17 12:46:54 +01:00
Mark Qvist ec7aa44a17 Updated version 2025-01-17 12:41:48 +01:00
Mark Qvist 4fa335639c Added T3S3 support to rnodeconf 2025-01-16 19:10:49 +01:00
Mark Qvist 67195c0b14 Improved logging 2025-01-16 17:49:50 +01:00
Mark Qvist ad1e6a41ee Improved daemon restart time on systems with many interfaces 2025-01-16 17:48:16 +01:00
Mark Qvist a56d93fc1e Fixed potential logging deadlock 2025-01-16 17:37:47 +01:00
Mark Qvist b8aa6a3e44 Improved LocalInterface detach 2025-01-16 15:57:43 +01:00
Mark Qvist 1709cd929a Improved interface detach on shared instance shutdown 2025-01-16 14:12:30 +01:00
Mark Qvist 4f4961257c Improved interface detach on shared instance shutdown 2025-01-16 14:09:18 +01:00
Mark Qvist 1b48f43a0d Corrected T3S3 SX1280 model codes in rnodeconf 2025-01-16 12:04:54 +01:00
Mark Qvist e5d446a54e Retry ratchet reload on potential I/O conflicts 2025-01-16 12:04:29 +01:00
Mark Qvist 0af768e742 Cleanup 2025-01-14 19:36:49 +01:00
Mark Qvist 1a7d20a8d6 Cleanup 2025-01-14 19:02:15 +01:00
Mark Qvist ec4f4d5a83 Cleanup 2025-01-14 18:57:02 +01:00
Mark Qvist 8cefa4b2a9 Improved resource transfer timing 2025-01-14 18:24:56 +01:00
Mark Qvist 2331f1ea3e Fixed link MTU clamping 2025-01-14 18:21:31 +01:00
Mark Qvist be7dafa30c Added MTU autoconfiguration on interfaces 2025-01-14 18:19:51 +01:00
Mark Qvist 3e20cb1b67 Added resource EIFR continuity to split resource handling 2025-01-14 18:19:07 +01:00
Mark Qvist 097e136662 Fixed rnstatus display bug 2025-01-14 18:18:27 +01:00
Mark Qvist e3a716224d Implemented MTU autoconfiguration on interfaces 2025-01-14 18:17:53 +01:00
Mark Qvist 80dc567a53 Handle negative time in time formatters 2025-01-14 12:45:17 +01:00
Mark Qvist c6576d6504 Added link MTU discovery configuration option 2025-01-14 00:13:56 +01:00
Mark Qvist 89d5d9517d Added print device config option to rnodeconf 2025-01-13 21:48:35 +01:00
Mark Qvist dc315653c0 Added interference status to RNodeInterface 2025-01-13 21:06:24 +01:00
Mark Qvist 746b403890 Noise floor output formatting 2025-01-13 16:37:18 +01:00
Mark Qvist fc619460f0 Updated manual 2025-01-13 15:42:46 +01:00
Mark Qvist cd0f82d9ad Updated tests 2025-01-13 15:42:32 +01:00
Mark Qvist 330c2aacac Fixed incorrect resource SDU calculation when link MTU is set 2025-01-13 14:42:03 +01:00
Mark Qvist 63da084bbe Updated docs 2025-01-13 14:41:38 +01:00
Mark Qvist cbbd8221ee Fixed typo 2025-01-13 14:41:21 +01:00
Mark Qvist 1d18d53052 Updated speedtest example 2025-01-12 23:48:56 +01:00
Mark Qvist ceccf3153b Correct link MDU calculation 2025-01-12 23:48:21 +01:00
Mark Qvist bde33e7d84 Added support for dynamic link MTU to Channel and Buffer 2025-01-12 23:26:18 +01:00
Mark Qvist 93330d96a0 Updated manual 2025-01-12 20:56:13 +01:00
Mark Qvist d93ce62878 Updated HW MTUs 2025-01-12 20:56:06 +01:00
Mark Qvist eafa4aefbb Added log format 2025-01-12 18:51:27 +01:00
Mark Qvist 53df2fa5e0 Improved profiler 2025-01-12 17:51:02 +01:00
Mark Qvist abc657806d Added cumulative utilisation to profiler 2025-01-12 17:32:11 +01:00
Mark Qvist a0f219f7f4 Last-hop LR MTU clamping 2025-01-12 17:31:17 +01:00
Mark Qvist 47eba03a4b Single HW_MTU field 2025-01-12 17:29:06 +01:00
Mark Qvist 3289cd1299 Cleanup 2025-01-12 17:28:32 +01:00
Mark Qvist ab5fcd7a5b Added live traffic stats counting and output to rnstatus 2025-01-11 19:30:00 +01:00
Mark Qvist 45494f21aa Allow IFAC bitmask generation for large packet sizes 2025-01-11 17:26:51 +01:00
Mark Qvist 5d677d2fb7 Set correct hardware MTU 2025-01-11 17:25:03 +01:00
Mark Qvist 808082e300 Link proof MTU 2025-01-11 17:24:39 +01:00
Mark Qvist 97cfdfd023 Unify link ID across versions regardless of MTU discovery support 2025-01-11 16:58:09 +01:00
Mark Qvist 9b15cf2295 Check link MTU discovery is enabled 2025-01-11 15:52:40 +01:00
Mark Qvist eaa68c2d04 Updated docs 2025-01-11 14:56:45 +01:00
Mark Qvist ac5ca78c77 Improved split resource transfer performance 2025-01-11 14:25:27 +01:00
Mark Qvist 5b17dbdfd6 Improved packet filter performance 2025-01-11 14:24:40 +01:00
Mark Qvist d4ed20c7d5 Improved rncp status output 2025-01-11 14:23:53 +01:00
Mark Qvist a5093ea8f0 Updated version 2025-01-11 13:22:49 +01:00
Mark Qvist f5cf438abd Improve resource transfer throughput on high-MTU links 2025-01-11 13:22:18 +01:00
Mark Qvist bf6e73e163 Path MTU discovery for links 2025-01-11 11:43:47 +01:00
Mark Qvist 503f475ca5 Read link MTU from link request packet 2025-01-11 03:12:31 +01:00
Mark Qvist 8506118aee Cleanup 2025-01-11 01:45:09 +01:00
Mark Qvist dfa295a90a Cleanup 2025-01-11 01:31:57 +01:00
Mark Qvist 3ace1583da Packets go brrrr 2025-01-11 01:26:46 +01:00
Mark Qvist c62b66195d Optimised profiler timing overhead 2025-01-10 21:37:45 +01:00
Mark Qvist b724836d2b Changed profiler to context manager 2025-01-10 20:07:17 +01:00
Mark Qvist 1e1b9dc79e Fixed missing check for dict entry existence 2025-01-10 12:40:11 +01:00
Mark Qvist c668a51e39 Cleanup 2025-01-10 12:39:25 +01:00
Mark Qvist 09b34d34c6 Only persist ratchets when new 2025-01-10 12:39:04 +01:00
Mark Qvist 54e18e41c5 Updated changelog 2025-01-10 11:42:30 +01:00
Mark Qvist 5550bca040 Updated manual 2025-01-10 11:42:06 +01:00
Mark Qvist f7a02351d4 Added interference avoidance configuration to rnodeconf 2025-01-09 17:46:12 +01:00
Mark Qvist 3125b99043 Cleanup 2025-01-09 15:21:59 +01:00
Mark Qvist 158765abb7 Added noise floor stat output to rnodeconf 2025-01-09 15:18:29 +01:00
Mark Qvist 81aa9ac5b6 Added Heltec T114 to rnodeconf 2025-01-09 15:17:41 +01:00
Mark Qvist 55f5842587 Added new channel stat and CSMA parameters to RNodeInterface 2025-01-09 15:15:54 +01:00
Mark Qvist 38dd63a99a Updated issue template 2025-01-06 11:38:37 +01:00
Mark Qvist 558cd6c4a7 Updated version 2025-01-06 11:38:29 +01:00
Mark Qvist 15e6a1bfde Add support for SX1280 with PA 2025-01-03 22:35:01 +01:00
Mark Qvist c1087e62fd Added ability to initiate display reconditioning to rnodeconf 2024-12-31 14:14:14 +01:00
Mark Qvist 9d924dcd6d Added ability to set display rotation to rnodeconf 2024-12-31 13:22:57 +01:00
Mark Qvist 163d2ed157 Fixed missing console image install on Heltec V3 2024-12-12 13:06:52 +01:00
Mark Qvist 68f07ddd38 Updated manual 2024-12-11 22:26:48 +01:00
Mark Qvist d956b93c13 Updated changelog 2024-12-11 22:26:41 +01:00
Mark Qvist 3036305662 Cleanup 2024-12-11 22:17:58 +01:00
Mark Qvist ee603ce68e Updated manual 2024-12-11 19:56:37 +01:00
Mark Qvist 989513cb46 Updated version 2024-12-11 19:41:35 +01:00
Mark Qvist 7e52c37580 Allow announce handler to receive announce packet hash 2024-12-11 19:18:02 +01:00
Mark Qvist 0984f92fa2 Fixed typo 2024-12-11 19:17:14 +01:00
Mark Qvist 2ab2d8e9df Updated changelog 2024-12-09 22:22:22 +01:00
Mark Qvist b828e0e858 Updated manual 2024-12-09 22:10:46 +01:00
Mark Qvist d4dd706bba Merge branch 'master' of github.com:markqvist/Reticulum 2024-12-08 14:27:37 +01:00
Mark Qvist ed30fa3e0a Added ability to reflect RNS logs to app-internal log handler callback 2024-12-08 14:27:17 +01:00
Mark Qvist 5e2b3df623 Added ability to run rnstatus as application-local imported module 2024-12-08 14:26:51 +01:00
Mark Qvist ae7dffdfc0 Added display read command to RNodeInterface 2024-12-08 14:25:58 +01:00
Mark Qvist 32b5c7a3af Updated documentation 2024-12-08 14:24:51 +01:00
markqvist 8b08658b7f Merge pull request #629 from jacobeva/refactor-fix
Fix RNodeMultiInterface to work with refactored interfaces
2024-12-07 22:32:27 +01:00
jacob.eva ee79c3a732 Fix RNodeMultiInterface to work with refactored interfaces 2024-12-07 21:28:14 +00:00
Mark Qvist 0e5f4aa08a Fixed missing artifact 2024-12-05 16:43:58 +01:00
Mark Qvist ec0407e5c8 Updated version 2024-12-05 16:40:53 +01:00
Mark Qvist db1380c413 Disable building manual 2024-12-05 16:36:44 +01:00
markqvist 7e3979dac0 Merge pull request #626 from gretel/add-revised-workflow
ci/cd: add release automation
2024-12-05 16:29:24 +01:00
Mark Qvist c1b6bde4a7 Updated documentation 2024-12-02 14:24:42 +01:00
Mark Qvist 8df89cc2d0 Allow dynamic sub-module import from compiled python bytecode 2024-12-02 14:20:34 +01:00
Mark Qvist 19adadf4cf Fixed imports for OpenWRT build 2024-12-01 09:09:39 +01:00
gretel c30feb3fc2 ci/cd: add release automation
Publishes a release when tagged with a `semver` version:
- X.Y.Z for "production quality" (1.0.0)
- X.Y.Z-suffix for development (1.0.0-alpha.1)

Release will be marked as 'prerelease' accordingly.

For now, any release will be marked 'draft'.
2024-11-30 21:43:54 +01:00
Mark Qvist 4c81589d5b Updated manual 2024-11-30 01:08:58 +01:00
Mark Qvist c014357e24 Updated documentation 2024-11-29 15:11:51 +01:00
Mark Qvist ec41dc1a03 Updated documentation 2024-11-29 15:11:47 +01:00
Mark Qvist 463dfa6fb4 Updated documentation 2024-11-29 15:10:35 +01:00
Mark Qvist 0354b5969d Updated documentation 2024-11-29 10:12:44 +01:00
Mark Qvist fc225bd55d Updated getting started and install instructions sections 2024-11-29 10:12:34 +01:00
Mark Qvist 67562126fc Refactored interface imports 2024-11-27 17:45:05 +01:00
Mark Qvist 9319d613f5 Updated documentation and manual 2024-11-24 14:34:43 +01:00
Mark Qvist 014994a788 Updated changelog 2024-11-24 14:34:38 +01:00
Mark Qvist 0f8efe3de1 Updated documentation and manual 2024-11-24 14:03:50 +01:00
Mark Qvist 274a8ca76a Fixed typo 2024-11-23 10:41:17 +01:00
Mark Qvist ea3ad6b287 Only attempt to get RNS status if a shared instance already exists 2024-11-22 23:11:57 +01:00
Mark Qvist f095b9cb8e Added init option for requiring existing shared instance 2024-11-22 23:11:34 +01:00
Mark Qvist 6f8d3e882a Updated docs and readme 2024-11-22 15:40:41 +01:00
Mark Qvist aabb763cea Refactored fernet to token 2024-11-22 15:19:12 +01:00
Mark Qvist 04d2626809 Updated docs and manual 2024-11-22 14:39:58 +01:00
Mark Qvist 823bfd537c Refactored processIncoming to process_incoming 2024-11-22 14:39:27 +01:00
Mark Qvist 434ebd2954 Fixed interface example bitrate init 2024-11-22 14:31:06 +01:00
Mark Qvist 44782c3429 Updated docs and manual 2024-11-22 14:25:18 +01:00
Mark Qvist 890846fa8d Added custom interfaces to documentation and readme 2024-11-22 14:16:53 +01:00
Mark Qvist 36c761e8dd Refactored processOutgoing to process_outgoing 2024-11-22 14:12:55 +01:00
Mark Qvist 4a4b625075 Implemented custom interface loading 2024-11-22 14:07:48 +01:00
Mark Qvist 4223203134 Added example custom interface 2024-11-22 14:07:17 +01:00
Mark Qvist e6966fe19a Cleanup 2024-11-22 12:16:29 +01:00
Mark Qvist e81c22cf53 Fixed spawned interface count sometimes being inaccurate on TCP and I2P interfaces 2024-11-22 12:02:18 +01:00
Mark Qvist c02e59e3ab Prepare interface modularity 2024-11-22 11:33:40 +01:00
Mark Qvist 5d5abf352b Prepare interface modularity 2024-11-22 11:27:46 +01:00
Mark Qvist ec9bb33d16 Apply KISS beacon frame length fix to Android-specific KISS interface 2024-11-22 11:20:28 +01:00
markqvist f3e836cec8 Merge pull request #618 from gretel/fix-kiss-callsign-beacon
Fix KISS beacon frame formatting and add sync pattern
2024-11-22 11:17:59 +01:00
Mark Qvist 8a50528111 Prepare interface modularity 2024-11-21 19:03:56 +01:00
gretel 9523595282 Fix KISS beacon frame length
Fix frame length handling to meet minimum length requirements (15 bytes) for
TNCs like Direwolf. Previously, raw beacon data was being sent directly,
causing frame length errors.

Changed code to pad beacon data with zeros to ensure minimum frame length.
2024-11-21 18:57:26 +01:00
Mark Qvist a762af035a Prepare interface modularity 2024-11-21 14:41:22 +01:00
Mark Qvist 760ab981d0 Prepare interface modularity for Android-specific interfaces 2024-11-21 13:51:34 +01:00
Mark Qvist 7b43ff0cef Cleanup 2024-11-21 13:13:41 +01:00
Mark Qvist 996161e2f4 Internal interface config handling for RNodeMultiInterface 2024-11-21 13:11:17 +01:00
Mark Qvist bf633bba5d Internal interface config handling for RNodeInterface 2024-11-21 13:03:03 +01:00
Mark Qvist 8337a5945d Internal interface config handling for AX25KISSInterface 2024-11-21 12:30:07 +01:00
Mark Qvist a736b3adfc Internal interface config handling for KISSInterface 2024-11-21 12:25:59 +01:00
Mark Qvist 25127cd3c9 Internal interface config handling for PipeInterface 2024-11-21 12:22:09 +01:00
Mark Qvist ebf084cff0 Internal interface config handling for SerialInterface 2024-11-21 12:16:44 +01:00
Mark Qvist cd8fe95d91 Internal interface config handling for I2PInterface 2024-11-21 12:10:21 +01:00
Mark Qvist e2efc61208 Added Yggdrasil example to interface documentation 2024-11-20 20:50:08 +01:00
Mark Qvist 5de63d5bf2 Internal interface config handling for TCPClientInterface 2024-11-20 20:39:44 +01:00
Mark Qvist c9d744f88a Internal interface config handling for TCPServerInterface 2024-11-20 20:27:01 +01:00
Mark Qvist 18e0dbddfa Internal interface config handling for UDPInterface 2024-11-20 20:20:40 +01:00
Mark Qvist 52c816cb27 Cleanup 2024-11-20 20:18:17 +01:00
Mark Qvist 582d2b91f5 Internal interface config handling for AutoInterface 2024-11-20 20:14:02 +01:00
Mark Qvist 28a0dbb0e0 Updated version 2024-11-20 19:56:02 +01:00
Mark Qvist 2895806541 Added IPv6 info to TCP interface documentation 2024-11-20 19:55:18 +01:00
Mark Qvist 5b8de73143 Correctly display IPv6 addresses in interface names 2024-11-20 19:24:06 +01:00
Mark Qvist 212af2f43b Automatically select IPv6 address for IPv6-only interfaces 2024-11-20 19:16:15 +01:00
Mark Qvist 1282061701 Add interface scope for link-local IPv6 addresses 2024-11-20 18:02:50 +01:00
Mark Qvist 49dba483a9 Use address structure according to target address family 2024-11-20 17:10:08 +01:00
Mark Qvist ebec63487f Added prefer_ipv6 option to TCPServerInterface 2024-11-20 16:53:14 +01:00
Mark Qvist 9373819234 Add ability to bind to AF_INET6 sockets based on both device name and IP addresses 2024-11-20 16:44:39 +01:00
markqvist 04925d8004 Merge pull request #601 from deavmi/patch-2
Allow binding to IPv6 (if present)
2024-11-20 14:28:46 +01:00
markqvist 4284084fef Merge pull request #600 from deavmi/patch-1
Determine AF FAMILY from getaddrinfo BEFORE socket ctor
2024-11-20 14:28:34 +01:00
Tristan B. Velloza Kildaire 63ad2afe3f Reapply "Allow binding to IPv6 (if present)"
This reverts commit 61712d322a.
2024-11-04 13:25:55 +02:00
Tristan B. Velloza Kildaire 61712d322a Revert "Allow binding to IPv6 (if present)"
This reverts commit f55004a574.
2024-11-04 13:25:46 +02:00
Tristan B. Velloza Kildaire 3599066356 Revert "Test"
This reverts commit 18c2a38b97.
2024-11-04 13:05:27 +02:00
Tristan B. Velloza Kildaire 18c2a38b97 Test 2024-11-04 13:02:45 +02:00
Tristan B. Velloza Kildaire f55004a574 Allow binding to IPv6 (if present)
If an interface has an IPv6 address record associated with it then, and only then, prefer that.

Otherwise AF_INET is used (Ipv4 address)
2024-11-03 17:54:59 +02:00
Tristan B. Velloza Kildaire 1768ddc459 Determine AF FAMILY from getaddrinfo BEFORE socket ctor
Before we call the `socket.socket(...)` constructor function, let us first provide `self.target_ip` and `self.target_port` to `socket.getaddrinfo(...)` (static function) and then get the AF family from it. Then we pass this into the ctor
2024-11-03 14:37:28 +02:00
Mark Qvist d002a75f34 Updated changelog 2024-10-20 14:09:12 +02:00
Mark Qvist 0b6d239551 Updated changelog 2024-10-20 14:07:54 +02:00
Mark Qvist 926b811a84 Updated docs 2024-10-20 14:04:48 +02:00
Mark Qvist 2bc8e11ad5 Updated version 2024-10-20 13:45:52 +02:00
Mark Qvist f5412f5c0b Fixed invalid link RSSI, SNR and Q data returned from API functions. Improved link physical layer stats updates. 2024-10-20 13:34:02 +02:00
Mark Qvist 5470f752b4 Cleanup 2024-10-20 12:26:54 +02:00
markqvist 48c006a94c Merge pull request #589 from faragher/master
Fixed file access bug, added fail-safe access
2024-10-20 12:18:23 +02:00
faragher 8445417661 Fixed file access bug, added fail-safe access 2024-10-19 12:39:48 -05:00
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
116 changed files with 8117 additions and 2363 deletions
+7 -3
View File
@@ -12,10 +12,14 @@ Before creating a bug report on this issue tracker, you **must** read the [Contr
- 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.
- After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), **delete this section only** (*"Read the Contribution Guidelines"*) from your bug report, **and fill in all the other sections**.
**Describe the Bug**
A clear and concise description of what the bug is.
First of all: Is this really a bug? Is it reproducible?
If this is a request for help because something is not working as you expected, stop right here, and go to the [discussions](https://github.com/markqvist/Reticulum/discussions) instead, where you can post your questions and get help from other users.
If this really is a bug or issue with the software, remove this section of the template, and provide **a clear and concise description of what the bug is**.
**To Reproduce**
Describe in detail how to reproduce the bug.
@@ -24,7 +28,7 @@ Describe in detail how to reproduce the bug.
A clear and concise description of what you expected to happen.
**Logs & Screenshots**
Please include any relevant log output. If applicable, also add screenshots to help explain your problem.
Please include any relevant log output. If applicable, also add screenshots to help explain your problem. In most cases, without any relevant log output, we will not be able to determine the cause of the bug, or reproduce it.
**System Information**
- OS and version
+96
View File
@@ -0,0 +1,96 @@
name: Build Reticulum
on:
push:
branches:
- '*'
tags:
- "[0-9]+.[0-9]+.[0-9]+*"
pull_request:
branches:
- master
paths-ignore:
- .gitignore
- LICENSE
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.x
- run: make test
package:
needs: test
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
environment: ${{ contains(github.ref, '-') && 'development' || 'production' }}
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.x
- run: |
python -m pip install -q build wheel setuptools
make remove_symlinks
make build_wheel
make build_pure_wheel
make create_symlinks
- uses: actions/upload-artifact@v4
with:
name: package
path: dist/*.whl
# documentation:
# needs: test
# if: startsWith(github.ref, 'refs/tags/')
# runs-on: ubuntu-latest
# environment: ${{ contains(github.ref, '-') && 'development' || 'production' }}
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-python@v5
# with:
# python-version: 3.x
# - run: |
# sudo apt-get -qq update && sudo apt-get -qq install latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended
# python -m pip -q install sphinx sphinx-copybutton
# cd docs && make latexpdf && make epub
# - uses: actions/upload-artifact@v4
# with:
# name: documentation
# path: |
# docs/build/latex/*.pdf
# docs/build/epub/*.epub
release:
needs: [package]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
environment: ${{ contains(github.ref, '-') && 'development' || 'production' }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: .artifacts
- uses: softprops/action-gh-release@v2
with:
files: |
# .artifacts/package/**.whl
# .artifacts/documentation/latex/reticulumnetworkstack.pdf
# .artifacts/documentation/epub/ReticulumNetworkStack.epub
draft: true
generate_release_notes: true
prerelease: ${{ contains(github.ref, '-') }}
fail_on_unmatched_files: true
+312 -1
View File
@@ -1,6 +1,317 @@
### 2025-01-19: RNS β 0.9.2
This maintenance release fixes a number of bugs.
**Changes**
- Fixed missing RX/TX bytes statistics assignment
- Fixed potential daemon thread IO buffer deadlock on externally mediated shutdown signal
- Fixed missing check for path announce emission timestamp in lower hop-count announce processing
**Release Hashes**
```
068eb4408b332ea6eec1a58fb4644fba3531c9ca10dcd79ecf893aaaf40e720d rns-0.9.2-py3-none-any.whl
1e7c123d244cc14c287568f3a99953cc11ffc1e79a72a029aa1be72fa8eff24e rnspure-0.9.2-py3-none-any.whl
```
### 2025-01-19: RNS β 0.9.1
This maintenance release adds reject signalling mechanism to resource transfers, fixes inconsistencies in the code examples, and improves thread configuration in the transport core.
**Changes**
- Added resource reject signalling
- Added error reporting on configured radio parameter mismatch on Android
- Improved thread configuration for transport core threads
- Updated examples
**Release Hashes**
```
49288a562ad6d4b5647c3afec051a6bb6497b75e3f165a972436134d4a93ad76 rns-0.9.1-py3-none-any.whl
abd6c4bdead2fc25d0b9b2cda5708586e8cb776b088f2a901a5f262e2ed901ae rnspure-0.9.1-py3-none-any.whl
```
### 2025-01-17: RNS β 0.9.0
This release lays the groundwork for future performance and resource utilisation optimisations. Most importantly, this release adds **link MTU autodiscovery**, which allow established links to use much higher MTUs than the base MTU of 500 bytes.
**Please note!** To actually use link MTU discovery, all transport nodes along the path must be upgraded to at least version `0.9.0`. Since this is the first release to add support for this feature, *it is currently **not** activated by default*, and no clients or applications will use it yet. Using link MTU autodiscovery by default will be enabled by default in RNS version `0.9.1`. Please upgrade your nodes!
Additionally, this release adds several new features, performance improvements and bug fixes, as well as support for RNodes running firmware version `1.81`.
**Changes**
- Added MTU autoconfiguration on interfaces that support higher MTUs
- Added link MTU autodiscovery and path clamping
- Added dynamic SDU calculations based on link MTU to `Resource`, `Channel` and `Buffer`
- Added resource EIFR continuity to split resource handling
- Added interference status to `RNodeInterface`
- Fixed a display bug in `rnstatus`
- Added live traffic stats to `rnstatus`
- Added T3S3 support to `rnodeconf`
- Added Heltec T114 support to `rnodeconf`
- Added LilyGO T-Echo support to `rnodeconf`
- Added option to print device configuration to `rnodeconf`
- Improved CPU utilisation and memory consumption
- Improved `rnsd` restart time on systems with many interfaces
- Improved `rncp` status output
- Improved packet filter performance
- Improved interface detachment handling
- Improved resource transfer timing and performance
- Improved Transport core efficiency
- Improved reliability of ratchet reloads if I/O conflicts occur
- Improved logging
- Improved built-in profiler
- Fixed a potential deadlock in logging
- Fixed time formatters not handling negative times
- Updated example code
**Release Hashes**
```
1ee60634cf0627c45b93f4e6c9adaf1fcdf9c1a8dfd4dd3dcd499e029554ab4f rns-0.9.0-py3-none-any.whl
b67eec583fdb224ba8174b317e66b8f7344e338e93760ed1a90f0bafea8cf09e rnspure-0.9.0-py3-none-any.whl
```
### 2025-01-09: RNS β 0.8.9
This maintenance release adds a number of configuration options to `rnodeconf`.
**Changes**
- Added noise floor output to `rnstatus` for supported interfaces
- Added channel noise floor and CSMA parameter reporting to `RNodeInterface`
- Added ability to set display rotation in `rnodeconf`
- Added ability to configure interference avoidance to `rnodeconf`
- Fixed missing console image install on Heltec V3 in `rnodeconf`
**Release Hashes**
```
b54fe8bc296f83a3a70569c9d1e9db3096249789c18f8d0217671479fa6881a1 rns-0.8.9-py3-none-any.whl
52fd992e5f9478d5a1f61f8f37dc0ee2d268fdd0b8a4e6656d33d632490afc5a rnspure-0.8.9-py3-none-any.whl
```
### 2024-12-11: RNS β 0.8.8
This maintenance release adds a single API function and fixes a bug.
**Changes**
- Allow announce handlers to receive announce packet hash
- Fix packet RSSI/SNR/Q cache not being available on standalone instances
**Release Hashes**
```
9c1755a81049c67b051ecb9fe4b2c5f7d98bf09d20ed52d6ce6a410298b0527b rns-0.8.8-py3-none-any.whl
d8871d69cde4b0a0b99b383f324d651dc77a2f44ec9641be828902c778a8d128 rnspure-0.8.8-py3-none-any.whl
```
### 2024-12-09: RNS β 0.8.7
This maintenance release adds support for OpenWRT packaging, and brings several minor improvements and bugfixes.
Thanks to @gretel and @jacobeva, who contributed to this release!
**Changes**
- Added support for packaging RNS to OpenWRT
- Added ability to run `rnstatus` as application-local imported module
- Added ability to reflect RNS log output to app-internal log handler callback
- Added display read functionality to `RNodeInterface`
- Fixed a regression in `RNodeMultiInterface` caused by earlier refactoring
- Imrpoved documentation
**Release Hashes**
```
e76ba8feeeae2c8df27e9906deebd7c721f0f0e887ad3fbd26df0212d6ce907a rns-0.8.7-py3-none-any.whl
046608539bc235d52c970c7f3c54e7aa01a86016ae00263f8a55fc796b6939f5 rnspure-0.8.7-py3-none-any.whl
```
### 2024-11-24: RNS β 0.8.6
This release adds full interface modularity and custom interface loading to RNS. Users can now easily create and use their own custom interfaces for communicating over practically anything. Support for IPv6 has also been added to the TCP-based interfaces.
In addition, several bugs have been fixed, and various internal improvements to code consistency and naming conventions have been carried out.
Thanks to @gretel and @deavmi, who contributed to this release!
**Changes**
- Added ability to load and configure custom, user-supplied interfaces
- Added IPv6 support to `TCPClientInterface` and `TCPServerInterface`
- Added an init option to the API for requiring an existing shared instance
- Changed `rnstatus` behaviour to only show status if Reticulum is already running
- Fixed `KISSInterface` beacon length for compatibility with software modems
- Fixed interface client count sometimes reporting incorrect values on TCP and I2P interfaces
- Refactored and improved interface initialisation and configuration handling
- Refactored interface code to be more consistent
- Refactored various deprecated references and names
- Updated documentation and manual
**Release Hashes**
```
60be127f003cd7838149bf8f01020206f829a7bd192706a608e39d8d7193d07b rns-0.8.6-py3-none-any.whl
d8701e19279d292b5b8af9da7c67b6ac88a992ca65109f8182c3e5c761a9ebeb rnspure-0.8.6-py3-none-any.whl
```
### 2024-10-20: RNS β 0.8.5
This maintenance release fixes a number of bugs. Thanks to @faragher for contributing to this release!
**Changes**
- Fixed missing close of file handles
- Fixed invalid values returned from `get_snr()` and `get_q()` physical layer stats API functions
**Release Hashes**
```
1757e809e083585bf4c23b6fe0f29954e5a1586ce14081099e38e606a75831df rns-0.8.5-py3-none-any.whl
44254630634f4dbb1ce3242247fe8180379d27bff15d183263b1856fd662f88d rnspure-0.8.5-py3-none-any.whl
```
### 2024-10-11: RNS β 0.8.4
This release fixes a number of bugs and improves reliability of automatic reconnection when BLE-connected RNodes unexpectedly disappear or lose connection.
**Changes**
- Improved RNode BLE reconnection realiability
- Added RNode battery state to `rnstatus` output
- Fixed resource transfer hanging for a long time over slow links if proof packet is lost
- Fixed missing import on Android
**Release Hashes**
```
d3f7a9fddc6c1e59b1e4895756fe602408ac6ef09de377ee65ec62d09fff97a3 rns-0.8.4-py3-none-any.whl
eb3843bcab1428be0adb097988991229a4c03156ab40cc9c6e2d9c590d8b850b rnspure-0.8.4-py3-none-any.whl
```
### 2024-10-10: RNS β 0.8.3
This release fixes a bug in resource transfer progress calculation, improves RNode error handling, and brings minor improvements to the `rncp` utility.
**Changes**
- Fixed a bug in resource transfer progress calculations
- Added physical layer transfer rate output option to `rncp`
- Added save directory option to `rncp`
- Improved path handling for the fetch-jail option of of `rncp`
- Added error detection for modem communication timeouts on connected RNode devices
**Release Hashes**
```
54ddab32769081045db5fe45b27492cc012bf2fad64bc65ed37011f3651469fb rns-0.8.3-py3-none-any.whl
a04915111d65b05a5f2ef2687ed208813034196c0c5e711cb01e6db72faa23ef rnspure-0.8.3-py3-none-any.whl
```
### 2024-10-06: RNS β 0.8.2
This release adds several new boards to `rnodeconf`, fixes a range of bugs and improves transport reliability.
Thanks to @jacobeva, @prusnak and @deavmi who contributed to this release!
**Changes**
- Added support for T-Beam Supreme devices to `rnodeconf`
- Added support for T3S3 devices to `rnodeconf`
- Added support for T-Deck devices to `rnodeconf`
- Added support for new hardware error codes from connected RNodes
- Added the ability to control the display on nRF52-based RNodes
- Improved resource transfers over very slow links, by adding more suitable `MAX_WINDOW` cap if link speed is continously below threshold.
- Improved `rnodeconf` flashing so manual resets for some devices are no longer required
- Added edge case handling for receiving a link proof after the link had timed out and been closed, but before it having been purged from active links table
- Updated supported hardware section of the manual with new boards
- Tuned path request timing for roaming instances
- Fixed a bug that caused RNS to fail to initialise in Termux on Android
- Fixed a bug in RNodeInterface firmware version comparison
- Fixed a bug in the serial framing of RNodeMultiInterface
- Fixed a bug in sub-interface spawning of RNodeMultiInterface
**Release Hashes**
```
db720a727a09c0c9d76288dec5a995a30146e65d6a4c5c034f47fb60a78f4962 rns-0.8.2-py3-none-any.whl
ee412535edba48817551658247fb0c843d17e1c97cad9d2a819a7fc627c5ba28 rnspure-0.8.2-py3-none-any.whl
```
### 2024-10-02: RNS β 0.8.1
This release adds BLE support to RNodeInterface, and support for configuring additional options to `rnodeconf`.
**Changes**
- Added Bluetooth Low Energy support to RNodeInterface
- Added RNode battery information to `rnstatus` output
- Added display blanking configuration to `rnodeconf`
- Added NeoPixel intensity configuration to `rnodeconf`
**Release Hashes**
```
f4b6b99b67d6b33b8a4562e5d5d5ac54c76814fff26e6c7a79950b82bd80123f rns-0.8.1-py3-none-any.whl
c2e540b4bf0f272bb51ae3e33a02f9c07f2619746d069d7ed83d88017bf7ea30 rnspure-0.8.1-py3-none-any.whl
```
### 2024-09-25: RNS β 0.8.0
This maintenance release improves the interface statistics API, and updates documentation.
**Changes**
- Added additional information to interface statistics
- Updated documentation
**Release Hashes**
```
fa5ff6d98230693be6805bb9a94585a6f54ec0af9cba15b771d4e676f140dc43 rns-0.8.0-py3-none-any.whl
ba20f688b69ae861c8aced251e10242a358fea15da6c22df10d4fc8846c9bf48 rnspure-0.8.0-py3-none-any.whl
```
### 2024-09-24: RNS β 0.7.9
This maintenance release improves transport reliability in certain (rare) cases.
**Changes**
- Added handling of a transport edge-case
**Release Hashes**
```
4c20c46df021d366386d497145024396f904666b0de22a92f9e5c937886ea39d rns-0.7.9-py3-none-any.whl
97d26282df929eca732a15523bc9d7f66387a93ffd911e8063c94c3f8f6ad73c rnspure-0.7.9-py3-none-any.whl
```
### 2024-09-18: RNS β 0.7.8
This maintenance release adds support for the openCom XL to `rnodeconf`, fixes a number of bugs, and also includes a few fine-tunings of timing parameters.
Thanks to @liamcottle and @jacobeva for contributing to this release!
**Changes**
- Added interface prioritisation according to reported bitrate
- Added support for openCom XL to `rnodeconf`
- Added performance profiler to built-in debugging tools
- Tuned link traffic timeouts
- Fixed a module import error in AX25KissInterface
- Fixed a missing exception on erroneous destination initialisation
**Release Hashes**
```
33fb9443e3b327d1a9125baa52d8ec3208a089dda62f749b819e0a94c06730f9 rns-0.7.8-py3-none-any.whl
cdced2adef4ead146239d0510fe2b9d62f69136bcd54b22d1080686fb56f9927 rnspure-0.7.8-py3-none-any.whl
```
### 2024-09-09: RNS β 0.7.7
This release adds support for automatic encryption key ratcheting for all packets, not just those sent over Reticulum links. In practical terms, this adds forward secrecy to packets sent with the raw `Packet` API.
In this release, the ratchets feature must be enabled on a per-destination basis by calling the `enable_ratchets` method on the relevant destination. In a future release, ratchets may become the default option, but for backwards-compatibility, it is currently optional. For more information, read the API documentation.
**Please note!** Versions of RNS prior to `0.7.7` will not be able to pass announces for destinations with ratchets enabled! If you use applications that can use ratchets (for example, LXMF version `0.5.0` and up), it is important that you update all transport instances on your network to `0.7.7`.
Thanks to @deavmi, @faragher, @jacobeva, @jeremy and @jeremybox for contributing to this release!
**Changes**
- Added key ratchet rotation and signalling
- Added ratchet API to documentation
- Added initial support for flashing T-Echo devices to `rnodeconf`
- Added remote management config options to example config
- Added automtic integration tests to source repository
- Fixed a regression that caused RNS not to work on Python versions lower than 3.10
- Fixed missing `establishment_rate` property init on Link objects
**Release Hashes**
```
0a3ab6dc82567a19adabe737358daee3002b60beda8ac0bf228f2a0c134ff6d8 rns-0.7.7-py3-none-any.whl
89b33fe9ab923139d3f5d43726d92817642be05a8c9d328c3becfc3c409e4b4b rnspure-0.7.7-py3-none-any.whl
```
### 2024-05-18: RNS β 0.7.6
This release add support for RNodes with multiple radio transceivers, courtesy of @jacobeva. It also brings a number of functionality and performance improvements, and fixes several bugs.
This release adds support for RNodes with multiple radio transceivers, courtesy of @jacobeva. It also brings a number of functionality and performance improvements, and fixes several bugs.
Thanks to @jacobeva, @faragher, @nathmo, @jschulthess and @liamcottle for contributing to this release!
+2 -1
View File
@@ -6,6 +6,7 @@
import argparse
import random
import sys
import RNS
# Let's define an app name. We'll use this for all
@@ -168,4 +169,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+1 -1
View File
@@ -118,4 +118,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+3 -4
View File
@@ -157,7 +157,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -254,9 +254,8 @@ def link_closed(link):
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
sys.exit(0)
# When the buffer has new data, read it and write it to the terminal.
def client_buffer_ready(ready_bytes: int):
@@ -320,4 +319,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+5 -6
View File
@@ -124,7 +124,7 @@ def server(configpath):
def server_loop(destination):
# Let the user know that everything is ready
RNS.log(
"Link example "+
"Channel example "+
RNS.prettyhexrep(destination.hash)+
" running, waiting for a connection."
)
@@ -212,7 +212,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -276,7 +276,7 @@ def client_loop():
packed_size = len(message.pack())
channel = server_link.get_channel()
if channel.is_ready_to_send():
if packed_size <= channel.MDU:
if packed_size <= channel.mdu:
channel.send(message)
else:
RNS.log(
@@ -321,9 +321,8 @@ def link_closed(link):
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
sys.exit(0)
# When a packet is received over the channel, we
# simply print out the data.
@@ -387,4 +386,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+5 -16
View File
@@ -5,8 +5,8 @@
# of the packet. #
##########################################################
import os
import argparse
import sys
import RNS
# Let's define an app name. We'll use this for all
@@ -28,19 +28,8 @@ def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Load identity from file if it exist or randomley create
if configpath:
ifilepath = "%s/storage/identitiesy/%s" % (configpath,APP_NAME)
else:
ifilepath = "%s/storage/identities/%s" % (RNS.Reticulum.configdir,APP_NAME)
if os.path.exists(ifilepath):
# Load identity from file
server_identity = RNS.Identity.from_file(ifilepath)
RNS.log("loaded identity from file: "+ifilepath, RNS.LOG_VERBOSE)
else:
# Randomly create a new identity for our echo example
server_identity = RNS.Identity()
RNS.log("created new identity", RNS.LOG_VERBOSE)
# Randomly create a new identity for our echo server
server_identity = RNS.Identity()
# We create a destination that clients can query. We want
# to be able to verify echo replies to our clients, so we
@@ -142,7 +131,7 @@ def client(destination_hexhash, configpath, timeout=None):
except Exception as e:
RNS.log("Invalid destination entered. Check your input!")
RNS.log(str(e)+"\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -340,4 +329,4 @@ if __name__ == "__main__":
client(args.destination, configarg, timeout=timeoutarg)
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+299
View File
@@ -0,0 +1,299 @@
# MIT License - Copyright (c) 2024 Mark Qvist / unsigned.io
# This example illustrates creating a custom interface
# definition, that can be loaded and used by Reticulum at
# runtime. Any number of custom interfaces can be created
# and loaded. To use the interface place it in the folder
# ~/.reticulum/interfaces, and add an interface entry to
# your Reticulum configuration file similar to this:
# [[Example Custom Interface]]
# type = ExampleInterface
# enabled = no
# mode = gateway
# port = /dev/ttyUSB0
# speed = 115200
# databits = 8
# parity = none
# stopbits = 1
from time import sleep
import sys
import threading
import time
# This HDLC helper class is used by the interface
# to delimit and packetize data over the physical
# medium - in this case a serial connection.
class HDLC():
# This example interface packetizes data using
# simplified HDLC framing, similar to PPP
FLAG = 0x7E
ESC = 0x7D
ESC_MASK = 0x20
@staticmethod
def escape(data):
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
return data
# Let's define our custom interface class. It must
# be a sub-class of the RNS "Interface" class.
class ExampleInterface(Interface):
# All interface classes must define a default
# IFAC size, used in IFAC setup when the user
# has not specified a custom IFAC size. This
# option is specified in bytes.
DEFAULT_IFAC_SIZE = 8
# The following properties are local to this
# particular interface implementation.
owner = None
port = None
speed = None
databits = None
parity = None
stopbits = None
serial = None
# All Reticulum interfaces must have an __init__
# method that takes 2 positional arguments:
# The owner RNS Transport instance, and a dict
# of configuration values.
def __init__(self, owner, configuration):
# The following lines demonstrate handling
# potential dependencies required for the
# interface to function correctly.
import importlib
if importlib.util.find_spec('serial') != None:
import serial
else:
RNS.log("Using this interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic()
# We start out by initialising the super-class
super().__init__()
# To make sure the configuration data is in the
# correct format, we parse it through the following
# method on the generic Interface class. This step
# is required to ensure compatibility on all the
# platforms that Reticulum supports.
ifconf = Interface.get_config_obj(configuration)
# Read the interface name from the configuration
# and set it on our interface instance.
name = ifconf["name"]
self.name = name
# We read configuration parameters from the supplied
# configuration data, and provide default values in
# case any are missing.
port = ifconf["port"] if "port" in ifconf else None
speed = int(ifconf["speed"]) if "speed" in ifconf else 9600
databits = int(ifconf["databits"]) if "databits" in ifconf else 8
parity = ifconf["parity"] if "parity" in ifconf else "N"
stopbits = int(ifconf["stopbits"]) if "stopbits" in ifconf else 1
# In case no port is specified, we abort setup by
# raising an exception.
if port == None:
raise ValueError(f"No port specified for {self}")
# All interfaces must supply a hardware MTU value
# to the RNS Transport instance. This value should
# be the maximum data packet payload size that the
# underlying medium is capable of handling in all
# cases without any segmentation.
self.HW_MTU = 564
# We initially set the "online" property to false,
# since the interface has not actually been fully
# initialised and connected yet.
self.online = False
# In this case, we can also set the indicated bit-
# rate of the interface to the serial port speed.
self.bitrate = speed
# Configure internal properties on the interface
# according to the supplied configuration.
self.pyserial = serial
self.serial = None
self.owner = owner
self.port = port
self.speed = speed
self.databits = databits
self.parity = serial.PARITY_NONE
self.stopbits = stopbits
self.timeout = 100
if parity.lower() == "e" or parity.lower() == "even":
self.parity = serial.PARITY_EVEN
if parity.lower() == "o" or parity.lower() == "odd":
self.parity = serial.PARITY_ODD
# Since all required parameters are now configured,
# we will try opening the serial port.
try:
self.open_port()
except Exception as e:
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e
# If opening the port succeeded, run any post-open
# configuration required.
if self.serial.is_open:
self.configure_device()
else:
raise IOError("Could not open serial port")
# Open the serial port with supplied configuration
# parameters and store a reference to the open port.
def open_port(self):
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
# The only thing required after opening the port
# is to wait a small amount of time for the
# hardware to initialise and then start a thread
# that reads any incoming data from the device.
def configure_device(self):
sleep(0.5)
thread = threading.Thread(target=self.read_loop)
thread.daemon = True
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
# This method will be called from our read-loop
# whenever a full packet has been received over
# the underlying medium.
def process_incoming(self, data):
# Update our received bytes counter
self.rxb += len(data)
# And send the data packet to the Transport
# instance for processing.
self.owner.inbound(data, self)
# The running Reticulum Transport instance will
# call this method on the interface whenever the
# interface must transmit a packet.
def process_outgoing(self,data):
if self.online:
# First, escape and packetize the data
# according to HDLC framing.
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
# Then write the framed data to the port
written = self.serial.write(data)
# Update the transmitted bytes counter
# and ensure that all data was written
self.txb += len(data)
if written != len(data):
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
# This read loop runs in a thread and continously
# receives bytes from the underlying serial port.
# When a full packet has been received, it will
# be sent to the process_incoming methed, which
# will in turn pass it to the Transport instance.
def read_loop(self):
try:
in_frame = False
escape = False
data_buffer = b""
last_read_ms = int(time.time()*1000)
while self.serial.is_open:
if self.serial.in_waiting:
byte = ord(self.serial.read(1))
last_read_ms = int(time.time()*1000)
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.process_incoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
if (escape):
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
byte = HDLC.FLAG
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
byte = HDLC.ESC
escape = False
data_buffer = data_buffer+bytes([byte])
else:
time_since_last = int(time.time()*1000) - last_read_ms
if len(data_buffer) > 0 and time_since_last > self.timeout:
data_buffer = b""
in_frame = False
escape = False
sleep(0.08)
except Exception as e:
self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False
self.serial.close()
self.reconnect_port()
# This method handles serial port disconnects.
def reconnect_port(self):
while not self.online:
try:
time.sleep(5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port()
if self.serial.is_open:
self.configure_device()
except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Reconnected serial port for "+str(self))
# Signal to Reticulum that this interface should
# not perform any ingress limiting.
def should_ingress_limit(self):
return False
# We must provide a string representation of this
# interface, that is used whenever the interface
# is printed in logs or external programs.
def __str__(self):
return "ExampleInterface["+self.name+"]"
# Finally, register the defined interface class as the
# target class for Reticulum to use as an interface
interface_class = ExampleInterface
+4 -5
View File
@@ -224,7 +224,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -462,7 +462,7 @@ def filelist_timeout_job():
global server_files
if len(server_files) == 0:
RNS.log("Timed out waiting for filelist, exiting")
os._exit(0)
sys.exit(0)
# When a link is closed, we'll inform the
@@ -475,9 +475,8 @@ def link_closed(link):
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
sys.exit(0)
# When RNS detects that the download has
# started, we'll update our menu state
@@ -601,4 +600,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+3 -4
View File
@@ -133,7 +133,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -245,9 +245,8 @@ def link_closed(link):
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
sys.exit(0)
# When a packet is received over the link, we
# simply print out the data.
@@ -311,4 +310,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+6 -25
View File
@@ -28,20 +28,8 @@ def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Load identity from file if it exist or randomley create
if configpath:
ifilepath = "%s/storage/identitiesy/%s" % (configpath,APP_NAME)
else:
ifilepath = "%s/storage/identities/%s" % (RNS.Reticulum.configdir,APP_NAME)
RNS.log("ifilepath: %s" % ifilepath)
if os.path.exists(ifilepath):
# Load identity from file
server_identity = RNS.Identity.from_file(ifilepath)
RNS.log("loaded identity from file: "+ifilepath, RNS.LOG_VERBOSE)
else:
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
RNS.log("created new identity", RNS.LOG_VERBOSE)
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
# We create a destination that clients can connect to. We
# want clients to create links to this destination, so we
@@ -70,7 +58,7 @@ def server_loop(destination):
" running, waiting for a connection."
)
RNS.log("Hit enter to manually send an announce (Ctrl-C or \"quit\" to quit)")
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server
@@ -80,12 +68,6 @@ def server_loop(destination):
entered = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
if entered == "quit":
if latest_client_link:
latest_client_link.teardown()
break
print("")
exit()
# When a client establishes a link to our server
# destination, this function will be called with
@@ -137,7 +119,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -240,9 +222,8 @@ def link_closed(link):
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
sys.exit(0)
# When a packet is received over the link, we
# simply print out the data.
@@ -306,4 +287,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+2 -1
View File
@@ -5,6 +5,7 @@
##########################################################
import argparse
import sys
import RNS
# Let's define an app name. We'll use this for all
@@ -98,4 +99,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+341
View File
@@ -0,0 +1,341 @@
##########################################################
# 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 sys
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)
# 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")
sys.exit(0)
# 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("")
sys.exit(0)
+3 -4
View File
@@ -119,7 +119,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -226,9 +226,8 @@ def link_closed(link):
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
sys.exit(0)
##########################################################
@@ -284,4 +283,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+10 -20
View File
@@ -149,8 +149,6 @@ def server_packet_received(message, packet):
time.sleep(0.2)
rc = 0
received_data = 0
# latest_client_link.teardown()
# os._exit(0)
##########################################################
@@ -159,6 +157,7 @@ def server_packet_received(message, packet):
# A reference to the server link
server_link = None
should_quit = False
# This initialisation is executed when the users chooses
# to run as a client
@@ -175,7 +174,7 @@ def client(destination_hexhash, configpath):
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -216,7 +215,7 @@ def client(destination_hexhash, configpath):
client_loop()
def client_loop():
global server_link
global server_link, should_quit
# Wait for the link to become active
while not server_link:
@@ -224,16 +223,7 @@ def client_loop():
should_quit = False
while not should_quit:
try:
text = input()
# Check if we should quit the example
if text == "quit" or text == "q" or text == "exit":
should_quit = True
server_link.teardown()
except Exception as e:
raise e
time.sleep(0.2)
# This function is called when a link
# has been established with the server
@@ -246,8 +236,8 @@ def link_established(link):
# Inform the user that the server is
# connected
RNS.log("Link established with server,sending...")
rd = os.urandom(RNS.Link.MDU)
RNS.log("Link established with server, sending...")
rd = os.urandom(link.mdu)
started = time.time()
while link.status == RNS.Link.ACTIVE and data_sent < data_cap*1.25:
RNS.Packet(server_link, rd, create_receipt=False).send()
@@ -276,17 +266,17 @@ def link_established(link):
# When a link is closed, we'll inform the
# user, and exit the program
def link_closed(link):
global should_quit
if link.teardown_reason == RNS.Link.TIMEOUT:
RNS.log("The link timed out, exiting now")
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
RNS.log("The link was closed by the server, exiting now")
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
should_quit = True
time.sleep(1.5)
os._exit(0)
sys.exit(0)
def client_packet_received(message, packet):
pass
@@ -344,4 +334,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("")
exit()
sys.exit(0)
+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...
+35 -20
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/build.yml"><img align="right" src="https://github.com/markqvist/Reticulum/actions/workflows/build.yml/badge.svg"/></a>
==========
<p align="center"><img width="200" src="https://raw.githubusercontent.com/markqvist/Reticulum/master/docs/source/graphics/rns_logo_512.png"></p>
@@ -41,21 +41,32 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
## Notable Features
- Coordination-less globally unique addressing and identification
- Fully self-configuring multi-hop routing
- Fully self-configuring multi-hop routing over heterogeneous carriers
- Flexible scalability over heterogeneous topologies
- Reticulum can carry data over any mixture of physical mediums and topologies
- Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks
- Initiator anonymity, communicate without revealing your identity
- Reticulum does not include source addresses on any packets
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
- Forward Secrecy with ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
- The foundational Reticulum Identity Keys are 512-bit Elliptic Curve keysets
- Forward Secrecy is available for all communication types, both for single packets and over links
- Reticulum uses the following format for encrypted tokens:
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
- Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
- AES-128 in CBC mode with PKCS7 padding
- HMAC using SHA256 for authentication
- IVs are generated through os.urandom()
- Unforgeable packet delivery confirmations
- A variety of supported interface types
- Flexible and extensible interface system
- Reticulum includes a large variety of built-in interface types
- Ability to load and utilise custom user- or community-supplied interface types
- Easily create your own custom interfaces for communicating over anything
- Authentication and virtual network segmentation on all supported interface types
- An intuitive and easy-to-use API
- Simpler and easier to use than sockets APIs and simpler, but more powerful
- Makes building distributed and decentralised applications much simpler
- Reliable and efficient transfer of arbitrary amounts of data
- Reticulum can handle a few bytes of data or files of many gigabytes
- Sequencing, 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
@@ -170,11 +181,12 @@ program.
Reticulum implements a range of generalised interface types that covers most of
the communications hardware that Reticulum can run over. If your hardware is
not supported, it's relatively simple to implement an interface class. I will
gratefully accept pull requests for custom interfaces if they are generally
useful.
not supported, it's [simple to implement a custom interface module](https://markqvist.github.io/Reticulum/manual/interfaces.html#custom-interfaces).
Currently, the following interfaces are supported:
Pull requests for custom interfaces are gratefully accepted, provided they are
generally useful and well-tested in real-world usage.
Currently, the following built-in interfaces are supported:
- Any Ethernet device
- LoRa using [RNode](https://unsigned.io/rnode/)
@@ -298,17 +310,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
@@ -317,12 +332,12 @@ In the default installation configuration, the `X25519`, `Ed25519` and
(via the [PyCA/cryptography](https://github.com/pyca/cryptography) package).
The hashing functions `SHA-256` and `SHA-512` are provided by the standard
Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `HKDF`,
`HMAC`, `Fernet` primitives, and the `PKCS7` padding function are always
`HMAC`, `Token` primitives, and the `PKCS7` padding function are always
provided by the following internal implementations:
- [HKDF.py](RNS/Cryptography/HKDF.py)
- [HMAC.py](RNS/Cryptography/HMAC.py)
- [Fernet.py](RNS/Cryptography/Fernet.py)
- [Token.py](RNS/Cryptography/Token.py)
- [PKCS7.py](RNS/Cryptography/PKCS7.py)
+3 -1
View File
@@ -45,7 +45,8 @@ class StreamDataMessage(MessageBase):
The stream id is limited to 2 bytes - 2 bit
"""
MAX_DATA_LEN = RNS.Link.MDU - 2 - 6 # 2 for stream data message header, 6 for channel envelope
OVERHEAD = 2 + 6 # 2 for stream data message header, 6 for channel envelope
MAX_DATA_LEN = RNS.Link.MDU - OVERHEAD
"""
When the Buffer package is imported, this value is
calculcated based on the value of OVERHEAD
@@ -215,6 +216,7 @@ class RawChannelWriter(RawIOBase, AbstractContextManager):
self._stream_id = stream_id
self._channel = channel
self._eof = False
self._mdu = channel.mdu - StreamDataMessage.OVERHEAD
def write(self, __b: bytes) -> int | None:
try:
+6 -3
View File
@@ -602,7 +602,7 @@ class Channel(contextlib.AbstractContextManager):
return envelope
@property
def MDU(self):
def mdu(self):
"""
Maximum Data Unit: the number of bytes available
for a message to consume in a single send. This
@@ -611,7 +611,10 @@ class Channel(contextlib.AbstractContextManager):
:return: number of bytes available
"""
return self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
mdu = self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
if mdu > 0xFFFF:
mdu = 0xFFFF
return mdu
class LinkChannelOutlet(ChannelOutletBase):
@@ -639,7 +642,7 @@ class LinkChannelOutlet(ChannelOutletBase):
@property
def mdu(self):
return self.link.MDU
return self.link.mdu
@property
def rtt(self):
+1 -1
View File
@@ -48,7 +48,7 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
derived = b""
for i in range(ceil(length / hash_len)):
block = hmac_sha256(pseudorandom_key, block + context + bytes([i + 1]))
block = hmac_sha256(pseudorandom_key, block + context + bytes([(i + 1)%(0xFF+1)]))
derived += block
return derived[:length]
@@ -27,7 +27,7 @@ from RNS.Cryptography import HMAC
from RNS.Cryptography import PKCS7
from RNS.Cryptography.AES import AES_128_CBC
class Fernet():
class Token():
"""
This class provides a slightly modified implementation of the Fernet spec
found at: https://github.com/fernet/spec/blob/master/Spec.md
@@ -37,7 +37,7 @@ class Fernet():
not relevant to Reticulum. They are therefore stripped from this
implementation, since they incur overhead and leak initiator metadata.
"""
FERNET_OVERHEAD = 48 # Bytes
TOKEN_OVERHEAD = 48 # Bytes
@staticmethod
def generate_key():
+5 -3
View File
@@ -5,7 +5,7 @@ from .Hashes import sha256
from .Hashes import sha512
from .HKDF import hkdf
from .PKCS7 import PKCS7
from .Fernet import Fernet
from .Token import Token
from .Provider import backend
import RNS.Cryptography.Provider as cp
@@ -20,5 +20,7 @@ elif cp.PROVIDER == cp.PROVIDER_PYCA:
from RNS.Cryptography.Proxies import Ed25519PrivateKeyProxy as Ed25519PrivateKey
from RNS.Cryptography.Proxies import Ed25519PublicKeyProxy as Ed25519PublicKey
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
+208 -24
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 RNS.Cryptography import Token
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,115 @@ 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:
def load_attempt():
ratchets_file = open(ratchets_path, "rb")
persisted_data = umsgpack.unpackb(ratchets_file.read())
if "signature" in persisted_data and "ratchets" in persisted_data:
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")
try:
try:
load_attempt()
except Exception as e:
RNS.trace_exception(e)
RNS.log(f"First ratchet reload attempt for {self} failed. Possible I/O conflict. Retrying in 500ms.", RNS.LOG_ERROR)
time.sleep(0.5)
load_attempt()
RNS.log(f"Ratchet reload retry succeeded", RNS.LOG_DEBUG)
except Exception as e:
self.ratchets = None
self.ratchets_path = None
RNS.trace_exception(e)
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
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.
@@ -371,9 +537,8 @@ class Destination:
raise TypeError("A single destination holds keys through an Identity instance")
if self.type == Destination.GROUP:
self.prv_bytes = Fernet.generate_key()
self.prv = Fernet(self.prv_bytes)
self.prv_bytes = Token.generate_key()
self.prv = Token(self.prv_bytes)
def get_private_key(self):
"""
@@ -388,7 +553,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.
@@ -404,7 +568,7 @@ class Destination:
if self.type == Destination.GROUP:
self.prv_bytes = key
self.prv = Fernet(self.prv_bytes)
self.prv = Token(self.prv_bytes)
def load_public_key(self, key):
if self.type != Destination.SINGLE:
@@ -412,7 +576,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 +587,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 +602,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 +613,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 +646,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.
+235 -42
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,11 +26,12 @@ import RNS
import time
import atexit
import hashlib
import threading
from .vendor import umsgpack as umsgpack
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
from RNS.Cryptography import Fernet
from RNS.Cryptography import Token
class Identity:
@@ -49,11 +50,23 @@ 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
TOKEN_OVERHEAD = RNS.Cryptography.Token.TOKEN_OVERHEAD
AES128_BLOCKSIZE = 16 # In bytes
HASHLENGTH = 256 # In bits
SIGLENGTH = KEYSIZE # In bits
@@ -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):
@@ -139,9 +155,9 @@ class Identity:
storage_known_destinations = {}
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
try:
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
storage_known_destinations = umsgpack.load(file)
file.close()
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
storage_known_destinations = umsgpack.load(file)
except:
pass
@@ -153,9 +169,9 @@ class Identity:
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_DEBUG)
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
umsgpack.dump(Identity.known_destinations, file)
file.close()
with open(RNS.Reticulum.storagepath+"/known_destinations","wb") as file:
umsgpack.dump(Identity.known_destinations, file)
save_time = time.time() - save_start
if save_time < 1:
@@ -175,9 +191,8 @@ class Identity:
def load_known_destinations():
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
try:
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
loaded_known_destinations = umsgpack.load(file)
file.close()
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
loaded_known_destinations = umsgpack.load(file)
Identity.known_destinations = {}
for known_destination in loaded_known_destinations:
@@ -197,7 +212,7 @@ class Identity:
Get a SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: SHA-256 hash as *bytes*
:returns: SHA-256 hash as *bytes*.
"""
return RNS.Cryptography.sha256(data)
@@ -207,7 +222,7 @@ class Identity:
Get a truncated SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash as *bytes*
:returns: Truncated SHA-256 hash as *bytes*.
"""
return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
@@ -217,24 +232,161 @@ 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):
try:
if destination_hash in Identity.known_ratchets and Identity.known_ratchets[destination_hash] == ratchet:
ratchet_exists = True
else:
ratchet_exists = False
if not ratchet_exists:
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity._get_ratchet_id(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_EXTREME)
Identity.known_ratchets[destination_hash] = ratchet
if not RNS.Transport.owner.is_connected_to_shared_instance:
def persist_job():
with Identity.ratchet_persist_lock:
hexhash = RNS.hexrep(destination_hash, delimit=False)
ratchet_data = {"ratchet": ratchet, "received": time.time()}
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
if not os.path.isdir(ratchetdir):
os.makedirs(ratchetdir)
outpath = f"{ratchetdir}/{hexhash}.out"
finalpath = f"{ratchetdir}/{hexhash}"
with open(outpath, "wb") as ratchet_file:
ratchet_file.write(umsgpack.packb(ratchet_data))
os.replace(outpath, finalpath)
threading.Thread(target=persist_job, daemon=True).start()
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:
with open(ratchet_path, "rb") as ratchet_file:
ratchet_data = umsgpack.unpackb(ratchet_file.read())
if time.time() < ratchet_data["received"]+Identity.RATCHET_EXPIRY and len(ratchet_data["ratchet"]) == Identity.RATCHETSIZE//8:
Identity.known_ratchets[destination_hash] = ratchet_data["ratchet"]
else:
return None
except Exception as e:
RNS.log(f"An error occurred while loading ratchet data for {RNS.prettyhexrep(destination_hash)} from storage.", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
return None
if destination_hash in Identity.known_ratchets:
return Identity.known_ratchets[destination_hash]
else:
RNS.log(f"Could not load ratchet for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG)
return None
@staticmethod
def validate_announce(packet, only_validate_signature=False):
try:
if packet.packet_type == RNS.Packet.ANNOUNCE:
keysize = Identity.KEYSIZE//8
ratchetsize = Identity.RATCHETSIZE//8
name_hash_len = Identity.NAME_HASH_LENGTH//8
sig_len = Identity.SIGLENGTH//8
destination_hash = packet.destination_hash
public_key = packet.data[:Identity.KEYSIZE//8]
name_hash = packet.data[Identity.KEYSIZE//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8]
random_hash = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10]
signature = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8]
app_data = b""
if len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:]
signed_data = destination_hash+public_key+name_hash+random_hash+app_data
# Get public key bytes from announce
public_key = packet.data[:keysize]
# If the packet context flag is set,
# this announce contains a new ratchet
if packet.context_flag == RNS.Packet.FLAG_SET:
name_hash = packet.data[keysize:keysize+name_hash_len ]
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
ratchet = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+ratchetsize]
signature = packet.data[keysize+name_hash_len+10+ratchetsize:keysize+name_hash_len+10+ratchetsize+sig_len]
app_data = b""
if len(packet.data) > keysize+name_hash_len+10+sig_len+ratchetsize:
app_data = packet.data[keysize+name_hash_len+10+sig_len+ratchetsize:]
# If the packet context flag is not set,
# this announce does not contain a ratchet
else:
ratchet = b""
name_hash = packet.data[keysize:keysize+name_hash_len]
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
signature = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+sig_len]
app_data = b""
if len(packet.data) > keysize+name_hash_len+10+sig_len:
app_data = packet.data[keysize+name_hash_len+10+sig_len:]
signed_data = destination_hash+public_key+name_hash+random_hash+ratchet+app_data
if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = None
@@ -281,6 +433,9 @@ class Identity:
else:
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME)
if ratchet:
Identity._remember_ratchet(destination_hash, ratchet)
return True
else:
@@ -469,7 +624,7 @@ class Identity:
def get_context(self):
return None
def encrypt(self, plaintext):
def encrypt(self, plaintext, ratchet=None):
"""
Encrypts information for the identity.
@@ -481,7 +636,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,
@@ -490,8 +650,8 @@ class Identity:
context=self.get_context(),
)
fernet = Fernet(derived_key)
ciphertext = fernet.encrypt(plaintext)
token = Token(derived_key)
ciphertext = token.encrypt(plaintext)
token = ephemeral_pub_bytes+ciphertext
return token
@@ -499,7 +659,7 @@ class Identity:
raise KeyError("Encryption failed because identity does not hold a public key")
def decrypt(self, ciphertext_token):
def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False, ratchet_id_receiver=None):
"""
Decrypts information for the identity.
@@ -513,22 +673,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(),
)
token = Token(derived_key)
plaintext = token.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(),
)
token = Token(derived_key)
plaintext = token.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:
+27 -7
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
@@ -59,6 +59,7 @@ class AX25():
class AX25KISSInterface(Interface):
MAX_CHUNK = 32768
BITRATE_GUESS = 1200
DEFAULT_IFAC_SIZE = 8
owner = None
port = None
@@ -68,7 +69,7 @@ class AX25KISSInterface(Interface):
stopbits = None
serial = None
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
def __init__(self, owner, configuration):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
@@ -79,6 +80,25 @@ class AX25KISSInterface(Interface):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
preamble = int(c["preamble"]) if "preamble" in c else None
txtail = int(c["txtail"]) if "txtail" in c else None
persistence = int(c["persistence"]) if "persistence" in c else None
slottime = int(c["slottime"]) if "slottime" in c else None
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
callsign = c["callsign"] if "callsign" in c else ""
ssid = int(c["ssid"]) if "ssid" in c else -1
if port == None:
raise ValueError("No port specified for serial interface")
self.HW_MTU = 564
self.pyserial = serial
@@ -96,7 +116,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
@@ -225,13 +245,13 @@ class AX25KISSInterface(Interface):
raise IOError("Could not enable AX.25 KISS interface flow control")
def processIncoming(self, data):
def process_incoming(self, data):
if (len(data) > AX25.HEADER_SIZE):
self.rxb += len(data)
self.owner.inbound(data[AX25.HEADER_SIZE:], self)
def processOutgoing(self,data):
def process_outgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
@@ -281,7 +301,7 @@ class AX25KISSInterface(Interface):
if len(self.packet_queue) > 0:
data = self.packet_queue.pop(0)
self.interface_ready = True
self.processOutgoing(data)
self.process_outgoing(data)
elif len(self.packet_queue) == 0:
self.interface_ready = True
@@ -300,7 +320,7 @@ class AX25KISSInterface(Interface):
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
+28 -6
View File
@@ -52,6 +52,7 @@ class KISS():
class KISSInterface(Interface):
MAX_CHUNK = 32768
BITRATE_GUESS = 1200
DEFAULT_IFAC_SIZE = 8
owner = None
port = None
@@ -61,7 +62,7 @@ class KISSInterface(Interface):
stopbits = None
serial = None
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data):
def __init__(self, owner, configuration):
import importlib
if RNS.vendor.platformutils.is_android():
self.on_android = True
@@ -83,6 +84,21 @@ class KISSInterface(Interface):
raise SystemError("Android-specific interface was used on non-Android OS")
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
preamble = int(c["preamble"]) if "preamble" in c else None
txtail = int(c["txtail"]) if "txtail" in c else None
persistence = int(c["persistence"]) if "persistence" in c else None
slottime = int(c["slottime"]) if "slottime" in c else None
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
beacon_interval = int(c["beacon_interval"]) if "beacon_interval" in c and c["beacon_interval"] != None else None
beacon_data = c["beacon_data"] if "beacon_data" in c else None
self.HW_MTU = 564
@@ -267,13 +283,13 @@ class KISSInterface(Interface):
raise IOError("Could not enable KISS interface flow control")
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
def af():
self.owner.inbound(data, self)
threading.Thread(target=af, daemon=True).start()
def processOutgoing(self,data):
def process_outgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
@@ -307,7 +323,7 @@ class KISSInterface(Interface):
if len(self.packet_queue) > 0:
data = self.packet_queue.pop(0)
self.interface_ready = True
self.processOutgoing(data)
self.process_outgoing(data)
elif len(self.packet_queue) == 0:
self.interface_ready = True
@@ -328,7 +344,7 @@ class KISSInterface(Interface):
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
@@ -373,7 +389,13 @@ class KISSInterface(Interface):
if time.time() > self.first_tx + self.beacon_i:
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.beacon_d.decode("utf-8")), RNS.LOG_DEBUG)
self.first_tx = None
self.processOutgoing(self.beacon_d)
# Pad to minimum length
frame = bytearray(self.beacon_d)
while len(frame) < 15:
frame.append(0x00)
self.process_outgoing(bytes(frame))
except Exception as e:
self.online = False
File diff suppressed because it is too large Load Diff
+16 -4
View File
@@ -42,6 +42,7 @@ class HDLC():
class SerialInterface(Interface):
MAX_CHUNK = 32768
DEFAULT_IFAC_SIZE = 8
owner = None
port = None
@@ -51,7 +52,7 @@ class SerialInterface(Interface):
stopbits = None
serial = None
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
def __init__(self, owner, configuration):
import importlib
if RNS.vendor.platformutils.is_android():
self.on_android = True
@@ -74,6 +75,17 @@ class SerialInterface(Interface):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
if port == None:
raise ValueError("No port specified for serial interface")
self.HW_MTU = 564
self.pyserial = serial
@@ -172,13 +184,13 @@ class SerialInterface(Interface):
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
def af():
self.owner.inbound(data, self)
threading.Thread(target=af, daemon=True).start()
def processOutgoing(self,data):
def process_outgoing(self,data):
if self.online:
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
written = self.serial.write(data)
@@ -202,7 +214,7 @@ class SerialInterface(Interface):
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
+4 -2
View File
@@ -23,5 +23,7 @@
import os
import glob
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
+36 -19
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from collections import deque
import socketserver
import threading
@@ -36,6 +36,7 @@ class AutoInterface(Interface):
DEFAULT_DISCOVERY_PORT = 29716
DEFAULT_DATA_PORT = 42671
DEFAULT_GROUP_ID = "reticulum".encode("utf-8")
DEFAULT_IFAC_SIZE = 16
SCOPE_LINK = "2"
SCOPE_ADMIN = "4"
@@ -86,7 +87,18 @@ class AutoInterface(Interface):
return socket.if_nametoindex(ifname)
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, multicast_address_type=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
def __init__(self, owner, configuration):
c = Interface.get_config_obj(configuration)
name = c["name"]
group_id = c["group_id"] if "group_id" in c else None
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
multicast_address_type = c["multicast_address_type"] if "multicast_address_type" in c else None
data_port = int(c["data_port"]) if "data_port" in c else None
allowed_interfaces = c.as_list("devices") if "devices" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
configured_bitrate = c["configured_bitrate"] if "configured_bitrate" in c else None
from RNS.vendor.ifaddr import niwrapper
super().__init__()
self.netinfo = niwrapper
@@ -287,7 +299,7 @@ class AutoInterface(Interface):
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
address = addr_info[0][4]
udp_server = socketserver.UDPServer(address, self.handler_factory(self.processIncoming))
udp_server = socketserver.UDPServer(address, self.handler_factory(self.process_incoming))
self.interface_servers[ifname] = udp_server
thread = threading.Thread(target=udp_server.serve_forever)
@@ -369,7 +381,7 @@ class AutoInterface(Interface):
RNS.log("Starting new UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.processIncoming))
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming))
self.interface_servers[ifname] = udp_server
thread = threading.Thread(target=udp_server.serve_forever)
@@ -443,22 +455,24 @@ class AutoInterface(Interface):
def refresh_peer(self, addr):
self.peers[addr][1] = time.time()
def processIncoming(self, data):
data_hash = RNS.Identity.full_hash(data)
deque_hit = False
if data_hash in self.mif_deque:
for te in self.mif_deque_times:
if te[0] == data_hash and time.time() < te[1]+AutoInterface.MULTI_IF_DEQUE_TTL:
deque_hit = True
break
def process_incoming(self, data):
if self.online:
data_hash = RNS.Identity.full_hash(data)
deque_hit = False
if data_hash in self.mif_deque:
for te in self.mif_deque_times:
if te[0] == data_hash and time.time() < te[1]+AutoInterface.MULTI_IF_DEQUE_TTL:
deque_hit = True
break
if not deque_hit:
self.mif_deque.append(data_hash)
self.mif_deque_times.append([data_hash, time.time()])
self.rxb += len(data)
self.owner.inbound(data, self)
if not deque_hit:
self.mif_deque.append(data_hash)
self.mif_deque_times.append([data_hash, time.time()])
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
def process_outgoing(self,data):
if self.online:
for peer in self.peers:
try:
if self.outbound_udp_socket == None:
@@ -480,6 +494,9 @@ class AutoInterface(Interface):
def should_ingress_limit(self):
return False
def detach(self):
self.online = False
def __str__(self):
return "AutoInterface["+self.name+"]"
+28 -12
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
import socketserver
import threading
import platform
@@ -627,14 +627,14 @@ class I2PInterfacePeer(Interface):
RNS.log("Attempt to reconnect on a non-initiator I2P interface. This should not happen.", RNS.LOG_ERROR)
raise IOError("Attempt to reconnect on a non-initiator I2P interface")
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self, data):
def process_outgoing(self, data):
if self.online:
while self.writing:
time.sleep(0.001)
@@ -732,7 +732,7 @@ class I2PInterfacePeer(Interface):
# Read loop for KISS framing
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
@@ -759,7 +759,7 @@ class I2PInterfacePeer(Interface):
# Read loop for HDLC framing
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
@@ -815,8 +815,8 @@ class I2PInterfacePeer(Interface):
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
if self.parent_interface.clients > 0:
self.parent_interface.clients -= 1
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
@@ -829,14 +829,28 @@ class I2PInterfacePeer(Interface):
class I2PInterface(Interface):
BITRATE_GUESS = 256*1000
DEFAULT_IFAC_SIZE = 16
def __init__(self, owner, name, rns_storagepath, peers, connectable = False, ifac_size = 16, ifac_netname = None, ifac_netkey = None):
@property
def clients(self):
return len(self.spawned_interfaces)
def __init__(self, owner, configuration):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
rns_storagepath = c["storagepath"]
peers = c.as_list("peers") if "peers" in c else None
connectable = c.as_bool("connectable") if "connectable" in c else False
ifac_size = c["ifac_size"] if "ifac_size" in c else None
ifac_netname = c["ifac_netname"] if "ifac_netname" in c else None
ifac_netkey = c["ifac_netkey"] if "ifac_netkey" in c else None
self.HW_MTU = 1064
self.online = False
self.clients = 0
self.spawned_interfaces = []
self.owner = owner
self.connectable = connectable
self.i2p_tunneled = True
@@ -956,10 +970,12 @@ class I2PInterface(Interface):
spawned_interface.HW_MTU = self.HW_MTU
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
self.clients += 1
while spawned_interface in self.spawned_interfaces:
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
spawned_interface.read_loop()
def processOutgoing(self, data):
def process_outgoing(self, data):
pass
def received_announce(self, from_spawned=False):
+48 -7
View File
@@ -24,6 +24,7 @@ import RNS
import time
import threading
from collections import deque
from RNS.vendor.configobj import ConfigObj
class Interface:
IN = False
@@ -63,12 +64,16 @@ class Interface:
IC_BURST_PENALTY = 5*60
IC_HELD_RELEASE_INTERVAL = 30
AUTOCONFIGURE_MTU = False
def __init__(self):
self.rxb = 0
self.txb = 0
self.created = time.time()
self.online = False
self.bitrate = 1e6
self.rxb = 0
self.txb = 0
self.created = time.time()
self.detached = False
self.online = False
self.bitrate = 62500
self.HW_MTU = None
self.ingress_control = True
self.ic_max_held_announces = Interface.MAX_HELD_ANNOUNCES
@@ -116,6 +121,31 @@ class Interface:
else:
return False
def optimise_mtu(self):
if self.AUTOCONFIGURE_MTU:
if self.bitrate > 16_000_000:
self.HW_MTU = 262144
elif self.bitrate > 8_000_000:
self.HW_MTU = 131072
elif self.bitrate > 4_000_000:
self.HW_MTU = 65536
elif self.bitrate > 2_000_000:
self.HW_MTU = 32768
elif self.bitrate > 1_000_000:
self.HW_MTU = 16384
elif self.bitrate > 500_000:
self.HW_MTU = 8192
elif self.bitrate > 250_000:
self.HW_MTU = 4096
elif self.bitrate > 125_000:
self.HW_MTU = 2048
elif self.bitrate > 62_500:
self.HW_MTU = 1024
else:
self.HW_MTU = None
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG) # TODO: Remove debug
def age(self):
return time.time()-self.created
@@ -222,7 +252,7 @@ class Interface:
wait_time = (tx_time / self.announce_cap)
self.announce_allowed_at = now + wait_time
self.processOutgoing(selected["raw"])
self.process_outgoing(selected["raw"])
self.sent_announce()
if selected in self.announce_queue:
@@ -238,4 +268,15 @@ class Interface:
RNS.log("The announce queue for this interface has been cleared.", RNS.LOG_ERROR)
def detach(self):
pass
pass
@staticmethod
def get_config_obj(config_in):
if type(config_in) == ConfigObj:
return config_in
else:
try:
return ConfigObj(config_in)
except Exception as e:
RNS.log(f"Could not parse supplied configuration data. The contained exception was: {e}", RNS.LOG_ERROR)
raise SystemError("Invalid configuration data supplied")
+32 -7
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
@@ -52,6 +52,7 @@ class KISS():
class KISSInterface(Interface):
MAX_CHUNK = 32768
BITRATE_GUESS = 1200
DEFAULT_IFAC_SIZE = 8
owner = None
port = None
@@ -61,7 +62,7 @@ class KISSInterface(Interface):
stopbits = None
serial = None
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data):
def __init__(self, owner, configuration):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
@@ -71,6 +72,24 @@ class KISSInterface(Interface):
RNS.panic()
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
preamble = int(c["preamble"]) if "preamble" in c else None
txtail = int(c["txtail"]) if "txtail" in c else None
persistence = int(c["persistence"]) if "persistence" in c else None
slottime = int(c["slottime"]) if "slottime" in c else None
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
beacon_interval = int(c["id_interval"]) if "id_interval" in c else None
beacon_data = c["id_callsign"] if "id_callsign" in c else None
if port == None:
raise ValueError("No port specified for serial interface")
self.HW_MTU = 564
@@ -217,12 +236,12 @@ class KISSInterface(Interface):
raise IOError("Could not enable KISS interface flow control")
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
def process_outgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
@@ -256,7 +275,7 @@ class KISSInterface(Interface):
if len(self.packet_queue) > 0:
data = self.packet_queue.pop(0)
self.interface_ready = True
self.processOutgoing(data)
self.process_outgoing(data)
elif len(self.packet_queue) == 0:
self.interface_ready = True
@@ -275,7 +294,7 @@ class KISSInterface(Interface):
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
@@ -319,7 +338,13 @@ class KISSInterface(Interface):
if time.time() > self.first_tx + self.beacon_i:
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.beacon_d.decode("utf-8")), RNS.LOG_DEBUG)
self.first_tx = None
self.processOutgoing(self.beacon_d)
# Pad to minimum length
frame = bytearray(self.beacon_d)
while len(frame) < 15:
frame.append(0x00)
self.process_outgoing(bytes(frame))
except Exception as e:
self.online = False
+39 -40
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
import socketserver
import threading
import socket
@@ -52,14 +52,12 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
class LocalClientInterface(Interface):
RECONNECT_WAIT = 8
AUTOCONFIGURE_MTU = True
def __init__(self, owner, name, target_port = None, connected_socket=None):
super().__init__()
# TODO: Remove at some point
# self.rxptime = 0
self.HW_MTU = 1064
self.HW_MTU = 262144
self.online = False
@@ -89,7 +87,7 @@ class LocalClientInterface(Interface):
self.connect()
self.owner = owner
self.bitrate = 1000*1000*1000
self.bitrate = 1_000_000_000
self.online = True
self.writing = False
@@ -152,21 +150,18 @@ class LocalClientInterface(Interface):
raise IOError("Attempt to reconnect on a non-initiator local interface")
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
# TODO: Remove at some point
# processing_start = time.time()
self.owner.inbound(data, self)
try:
self.owner.inbound(data, self)
except Exception as e:
RNS.log(f"An error in the processing of an incoming frame for {self}: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
# TODO: Remove at some point
# duration = time.time() - processing_start
# self.rxptime += duration
def processOutgoing(self, data):
def process_outgoing(self, data):
if self.online:
try:
self.writing = True
@@ -176,8 +171,8 @@ class LocalClientInterface(Interface):
self.send_lock = Lock()
with self.send_lock:
# RNS.log(f"Simulating latency of {RNS.prettytime(s)} for {len(data)} bytes", RNS.LOG_EXTREME)
s = len(data) / self.bitrate * 8
RNS.log(f"Simulating latency of {RNS.prettytime(s)} for {len(data)} bytes")
time.sleep(s)
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
@@ -190,6 +185,7 @@ class LocalClientInterface(Interface):
except Exception as e:
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
self.teardown()
@@ -197,32 +193,31 @@ class LocalClientInterface(Interface):
try:
in_frame = False
escape = False
frame_buffer = b""
data_in = b""
data_buffer = b""
while True:
data_in = self.socket.recv(4096)
if len(data_in) > 0:
pointer = 0
while pointer < len(data_in):
byte = data_in[pointer]
pointer += 1
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
frame_buffer += data_in
flags_remaining = True
while flags_remaining:
frame_start = frame_buffer.find(HDLC.FLAG)
if frame_start != -1:
frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
if frame_end != -1:
frame = frame_buffer[frame_start+1:frame_end]
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
self.process_incoming(frame)
frame_buffer = frame_buffer[frame_end:]
else:
if (escape):
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
byte = HDLC.FLAG
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
byte = HDLC.ESC
escape = False
data_buffer = data_buffer+bytes([byte])
flags_remaining = False
else:
flags_remaining = False
else:
self.online = False
if self.is_connected_to_shared_instance and not self.detached:
@@ -249,12 +244,14 @@ class LocalClientInterface(Interface):
self.detached = True
try:
self.socket.shutdown(socket.SHUT_RDWR)
if self.socket != None:
self.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
self.socket.close()
if self.socket != None:
self.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
@@ -292,6 +289,7 @@ class LocalClientInterface(Interface):
class LocalServerInterface(Interface):
AUTOCONFIGURE_MTU = True
def __init__(self, owner, bindport=None):
super().__init__()
@@ -319,6 +317,7 @@ class LocalServerInterface(Interface):
address = (self.bind_ip, self.bind_port)
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
self.server.daemon_threads = True
thread = threading.Thread(target=self.server.serve_forever)
thread.daemon = True
@@ -350,7 +349,7 @@ class LocalServerInterface(Interface):
self.clients += 1
spawned_interface.read_loop()
def processOutgoing(self, data):
def process_outgoing(self, data):
pass
def received_announce(self, from_spawned=False):
+16 -7
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
@@ -46,16 +46,25 @@ class HDLC():
class PipeInterface(Interface):
MAX_CHUNK = 32768
BITRATE_GUESS = 1*1000*1000
DEFAULT_IFAC_SIZE = 8
owner = None
command = None
def __init__(self, owner, name, command, respawn_delay):
def __init__(self, owner, configuration):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
command = c["command"] if "command" in c else None
respawn_delay = c.as_float("respawn_delay") if "respawn_delay" in c else None
if command == None:
raise ValueError("No command specified for PipeInterface")
if respawn_delay == None:
respawn_delay = 5
super().__init__()
self.HW_MTU = 1064
self.owner = owner
@@ -101,12 +110,12 @@ class PipeInterface(Interface):
RNS.log("Subprocess pipe for "+str(self)+" is now connected", RNS.LOG_VERBOSE)
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
def process_outgoing(self,data):
if self.online:
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
written = self.process.stdin.write(data)
@@ -134,7 +143,7 @@ class PipeInterface(Interface):
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
+497 -40
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
@@ -54,10 +54,13 @@ class KISS():
CMD_STAT_SNR = 0x24
CMD_STAT_CHTM = 0x25
CMD_STAT_PHYPRM = 0x26
CMD_STAT_BAT = 0x27
CMD_STAT_CSMA = 0x28
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_FB_EXT = 0x41
CMD_FB_READ = 0x42
CMD_DISP_READ = 0x66
CMD_FB_WRITE = 0x43
CMD_BT_CTRL = 0x46
CMD_PLATFORM = 0x48
@@ -77,9 +80,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):
@@ -90,6 +97,7 @@ class KISS():
class RNodeInterface(Interface):
MAX_CHUNK = 32768
DEFAULT_IFAC_SIZE = 8
FREQ_MIN = 137000000
FREQ_MAX = 3000000000
@@ -107,7 +115,14 @@ class RNodeInterface(Interface):
Q_SNR_MAX = 6
Q_SNR_STEP = 2
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None, st_alock = None, lt_alock = None):
BATTERY_STATE_UNKNOWN = 0x00
BATTERY_STATE_DISCHARGING = 0x01
BATTERY_STATE_CHARGING = 0x02
BATTERY_STATE_CHARGED = 0x03
DISPLAY_READ_INTERVAL = 1.0
def __init__(self, owner, configuration):
if RNS.vendor.platformutils.is_android():
raise SystemError("Invalid interface type. The Android-specific RNode interface must be used on Android")
@@ -121,6 +136,41 @@ class RNodeInterface(Interface):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
frequency = int(c["frequency"]) if "frequency" in c else 0
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else 0
txpower = int(c["txpower"]) if "txpower" in c else 0
sf = int(c["spreadingfactor"]) if "spreadingfactor" in c else 0
cr = int(c["codingrate"]) if "codingrate" in c else 0
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
id_interval = int(c["id_interval"]) if "id_interval" in c else None
id_callsign = c["id_callsign"] if "id_callsign" in c else None
st_alock = float(c["airtime_limit_short"]) if "airtime_limit_short" in c else None
lt_alock = float(c["airtime_limit_long"]) if "airtime_limit_long" in c else None
force_ble = False
ble_name = None
ble_addr = None
port = c["port"] if "port" in c else None
if port == None:
raise ValueError("No port specified for RNode interface")
if port != None:
ble_uri_scheme = "ble://"
if port.lower().startswith(ble_uri_scheme):
force_ble = True
ble_string = port[len(ble_uri_scheme):]
port = None
if len(ble_string) == 0:
pass
elif len(ble_string.split(":")) == 6 and len(ble_string) == 17:
ble_addr = ble_string
else:
ble_name = ble_string
self.HW_MTU = 508
self.pyserial = serial
@@ -135,6 +185,16 @@ class RNodeInterface(Interface):
self.online = False
self.detached = False
self.reconnecting= False
self.hw_errors = []
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
@@ -175,16 +235,38 @@ class RNodeInterface(Interface):
self.r_airtime_long = 0.0
self.r_channel_load_short = 0.0
self.r_channel_load_long = 0.0
self.r_symbol_time_ms = None
self.r_symbol_rate = None
self.r_preamble_symbols = None
self.r_premable_time_ms = None
self.r_symbol_time_ms = None
self.r_symbol_rate = None
self.r_preamble_symbols = None
self.r_premable_time_ms = None
self.r_csma_slot_time_ms = None
self.r_csma_difs_ms = None
self.r_csma_cw_band = None
self.r_csma_cw_min = None
self.r_csma_cw_max = None
self.r_current_rssi = None
self.r_noise_floor = None
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
self.r_battery_percent = 0
self.r_framebuffer = b""
self.r_framebuffer_readtime = 0
self.r_framebuffer_latency = 0
self.r_disp = b""
self.r_disp_readtime = 0
self.r_disp_latency = 0
self.should_read_display = False
self.read_display_interval = RNodeInterface.DISPLAY_READ_INTERVAL
self.packet_queue = []
self.flow_control = flow_control
self.interface_ready = False
self.announce_rate_target = None
if force_ble or self.ble_addr != None or self.ble_name != None:
self.use_ble = True
self.validcfg = True
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
@@ -248,23 +330,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
@@ -272,6 +369,10 @@ class RNodeInterface(Interface):
self.r_cr = None
self.r_state = None
self.r_lock = None
self.detected = False
def configure_device(self):
self.reset_radio_state()
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
@@ -279,13 +380,23 @@ class RNodeInterface(Interface):
thread.start()
self.detect()
sleep(0.2)
if not self.use_ble:
sleep(0.2)
else:
ble_detect_timeout = 5
detect_time = time.time()
while not self.detected and time.time() < detect_time + ble_detect_timeout:
time.sleep(0.1)
if self.detected:
detect_time = RNS.prettytime(time.time()-detect_time)
else:
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
if not self.detected:
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
self.serial.close()
else:
if self.platform == KISS.PLATFORM_ESP32:
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
self.display = True
RNS.log("Serial port "+self.port+" is now open")
@@ -313,6 +424,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)
@@ -361,7 +475,34 @@ class RNodeInterface(Interface):
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while writing framebuffer data device")
raise IOError("An IO error occurred while writing framebuffer data to device")
def read_framebuffer(self):
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FB_READ])+bytes([0x01])+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
self.r_framebuffer_readtime = time.time()
if written != len(kiss_command):
raise IOError("An IO error occurred while sending framebuffer read command")
def read_display(self):
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_READ])+bytes([0x01])+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
self.r_disp_readtime = time.time()
if written != len(kiss_command):
raise IOError("An IO error occurred while sending display read command")
def _read_display_job(self):
while self.should_read_display:
self.read_display()
time.sleep(self.read_display_interval)
def start_display_updates(self):
if not self.should_read_display:
self.should_read_display = True
threading.Thread(target=self._read_display_job, daemon=True).start()
def stop_display_updates(self):
self.should_read_display = False
def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
@@ -447,9 +588,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
@@ -462,7 +606,14 @@ class RNodeInterface(Interface):
def validateRadioState(self):
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
sleep(0.25);
if self.use_ble:
sleep(1.00)
else:
sleep(0.25)
if self.use_ble and self.ble != None and self.ble.device_disappeared:
RNS.log(f"Device disappeared during radio state validation for {self}", RNS.LOG_ERROR)
return False
self.validcfg = True
if (self.r_frequency != None and abs(self.frequency - int(self.r_frequency)) > 100):
@@ -495,14 +646,14 @@ class RNodeInterface(Interface):
except:
self.bitrate = 0
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
self.r_stat_rssi = None
self.r_stat_snr = None
def processOutgoing(self,data):
def process_outgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
@@ -533,7 +684,7 @@ class RNodeInterface(Interface):
if len(self.packet_queue) > 0:
data = self.packet_queue.pop(0)
self.interface_ready = True
self.processOutgoing(data)
self.process_outgoing(data)
elif len(self.packet_queue) == 0:
self.interface_ready = True
@@ -553,7 +704,7 @@ class RNodeInterface(Interface):
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
data_buffer = b""
command_buffer = b""
elif (byte == KISS.FEND):
@@ -622,7 +773,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)
@@ -729,16 +879,31 @@ class RNodeInterface(Interface):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 8):
if (len(command_buffer) == 11):
ats = command_buffer[0] << 8 | command_buffer[1]
atl = command_buffer[2] << 8 | command_buffer[3]
cus = command_buffer[4] << 8 | command_buffer[5]
cul = command_buffer[6] << 8 | command_buffer[7]
crs = command_buffer[8]
nfl = command_buffer[9]
ntf = command_buffer[10]
self.r_airtime_short = ats/100.0
self.r_airtime_long = atl/100.0
self.r_channel_load_short = cus/100.0
self.r_channel_load_long = cul/100.0
self.r_current_rssi = crs-RNodeInterface.RSSI_OFFSET
self.r_noise_floor = nfl-RNodeInterface.RSSI_OFFSET
if ntf == 0xFF:
self.r_interference = None
else:
self.r_interference = ntf-RNodeInterface.RSSI_OFFSET
if self.r_interference != None:
RNS.log(f"{self} Radio detected interference at {self.r_interference} dBm", RNS.LOG_DEBUG)
# TODO: Remove debug
# RNS.log(f"RSSI: {self.r_current_rssi}, Noise floor: {self.r_noise_floor}, Interference: {self.r_interference}", RNS.LOG_EXTREME)
elif (command == KISS.CMD_STAT_PHYPRM):
if (byte == KISS.FESC):
escape = True
@@ -750,22 +915,68 @@ class RNodeInterface(Interface):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 10):
if (len(command_buffer) == 12):
lst = (command_buffer[0] << 8 | command_buffer[1])/1000.0
lsr = command_buffer[2] << 8 | command_buffer[3]
prs = command_buffer[4] << 8 | command_buffer[5]
prt = command_buffer[6] << 8 | command_buffer[7]
cst = command_buffer[8] << 8 | command_buffer[9]
dft = command_buffer[10] << 8 | command_buffer[11]
if lst != self.r_symbol_time_ms or lsr != self.r_symbol_rate or prs != self.r_preamble_symbols or prt != self.r_premable_time_ms or cst != self.r_csma_slot_time_ms:
if lst != self.r_symbol_time_ms or lsr != self.r_symbol_rate or prs != self.r_preamble_symbols or prt != self.r_premable_time_ms or cst != self.r_csma_slot_time_ms or dft != self.r_csma_difs_ms:
self.r_symbol_time_ms = lst
self.r_symbol_rate = lsr
self.r_preamble_symbols = prs
self.r_premable_time_ms = prt
self.r_csma_slot_time_ms = cst
RNS.log(str(self)+" Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms (at "+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
RNS.log(str(self)+" Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
RNS.log(str(self)+" Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
self.r_csma_difs_ms = dft
RNS.log(f"{self} Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms ("+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
RNS.log(f"{self} Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
RNS.log(f"{self} Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
RNS.log(f"{self} Radio reporting DIFS time is "+str(self.r_csma_difs_ms)+"ms", RNS.LOG_DEBUG)
elif (command == KISS.CMD_STAT_CSMA):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 3):
cbw = command_buffer[0]
cbl = command_buffer[1]
cbh = command_buffer[2]
if cbw != self.r_csma_cw_band or cbl != self.r_csma_cw_min or cbh != self.r_csma_cw_max:
self.r_csma_cw_band = cbw
self.r_csma_cw_min = cbl
self.r_csma_cw_max = cbh
# TODO: Remove debug
# RNS.log(f"{self} Radio reporting contention window band is {self.r_csma_cw_band}", RNS.LOG_EXTREME)
# RNS.log(f"{self} Radio reporting minimum contention window is {self.r_csma_cw_min}", RNS.LOG_EXTREME)
# RNS.log(f"{self} Radio reporting maximum contention window is {self.r_csma_cw_max}", RNS.LOG_EXTREME)
elif (command == KISS.CMD_STAT_BAT):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 2):
bat_percent = command_buffer[1]
if bat_percent > 100:
bat_percent = 100
if bat_percent < 0:
bat_percent = 0
self.r_battery_state = command_buffer[0]
self.r_battery_percent = bat_percent
elif (command == KISS.CMD_RANDOM):
self.r_random = byte
elif (command == KISS.CMD_PLATFORM):
@@ -779,6 +990,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")
@@ -790,6 +1007,36 @@ class RNodeInterface(Interface):
raise IOError("ESP32 reset")
elif (command == KISS.CMD_READY):
self.process_queue()
elif (command == KISS.CMD_FB_READ):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 512):
self.r_framebuffer_latency = time.time() - self.r_framebuffer_readtime
self.r_framebuffer = command_buffer
elif (command == KISS.CMD_DISP_READ):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 1024):
self.r_disp_latency = time.time() - self.r_disp_readtime
self.r_disp = command_buffer
elif (command == KISS.CMD_DETECT):
if byte == KISS.DETECT_RESP:
self.detected = True
@@ -809,7 +1056,7 @@ class RNodeInterface(Interface):
if self.first_tx != None:
if time.time() > self.first_tx + self.id_interval:
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
self.processOutgoing(self.id_callsign)
self.process_outgoing(self.id_callsign)
sleep(0.08)
@@ -853,9 +1100,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
+165 -87
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
@@ -70,7 +70,7 @@ class KISS():
CMD_INT1_DATA = 0x10
CMD_INT2_DATA = 0x20
CMD_INT3_DATA = 0x70
CMD_INT4_DATA = 0x80
CMD_INT4_DATA = 0x75
CMD_INT5_DATA = 0x90
CMD_INT6_DATA = 0xA0
CMD_INT7_DATA = 0xB0
@@ -82,8 +82,8 @@ class KISS():
CMD_SEL_INT0 = 0x1E
CMD_SEL_INT1 = 0x1F
CMD_SEL_INT2 = 0x2F
CMD_SEL_INT3 = 0x7F
CMD_SEL_INT4 = 0x8F
CMD_SEL_INT3 = 0x74
CMD_SEL_INT4 = 0x7F
CMD_SEL_INT5 = 0x9F
CMD_SEL_INT6 = 0xAF
CMD_SEL_INT7 = 0xBF
@@ -106,6 +106,7 @@ class KISS():
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
PLATFORM_NRF52 = 0x70
SX127X = 0x00
SX1276 = 0x01
@@ -116,40 +117,42 @@ class KISS():
SX1280 = 0x21
def int_data_cmd_to_index(int_data_cmd):
match int_data_cmd:
case KISS.CMD_INT0_DATA:
return 0
case KISS.CMD_INT1_DATA:
return 1
case KISS.CMD_INT2_DATA:
return 2
case KISS.CMD_INT3_DATA:
return 3
case KISS.CMD_INT4_DATA:
return 4
case KISS.CMD_INT5_DATA:
return 5
case KISS.CMD_INT6_DATA:
return 6
case KISS.CMD_INT7_DATA:
return 7
case KISS.CMD_INT8_DATA:
return 8
case KISS.CMD_INT9_DATA:
return 9
case KISS.CMD_INT10_DATA:
return 10
case KISS.CMD_INT11_DATA:
return 11
if int_data_cmd == KISS.CMD_INT0_DATA:
return 0
elif int_data_cmd == KISS.CMD_INT1_DATA:
return 1
elif int_data_cmd == KISS.CMD_INT2_DATA:
return 2
elif int_data_cmd == KISS.CMD_INT3_DATA:
return 3
elif int_data_cmd == KISS.CMD_INT4_DATA:
return 4
elif int_data_cmd == KISS.CMD_INT5_DATA:
return 5
elif int_data_cmd == KISS.CMD_INT6_DATA:
return 6
elif int_data_cmd == KISS.CMD_INT7_DATA:
return 7
elif int_data_cmd == KISS.CMD_INT8_DATA:
return 8
elif int_data_cmd == KISS.CMD_INT9_DATA:
return 9
elif int_data_cmd == KISS.CMD_INT10_DATA:
return 10
elif int_data_cmd == KISS.CMD_INT11_DATA:
return 11
else:
return 0
def interface_type_to_str(interface_type):
match interface_type:
case KISS.SX126X | KISS.SX1262:
if interface_type == KISS.SX126X or interface_type == KISS.SX1262:
return "SX126X"
case KISS.SX127X | KISS.SX1276 | KISS.SX1278:
return "SX127X"
case KISS.SX128X | KISS.SX1280:
return "SX128X"
elif interface_type == KISS.SX127X or interface_type == KISS.SX1276 or interface_type == KISS.SX1278:
return "SX127X"
elif interface_type == KISS.SX128X or interface_type == KISS.SX1280:
return "SX128X"
else:
return "SX127X"
@staticmethod
def escape(data):
@@ -160,17 +163,18 @@ class KISS():
class RNodeMultiInterface(Interface):
MAX_CHUNK = 32768
DEFAULT_IFAC_SIZE = 8
CALLSIGN_MAX_LEN = 32
REQUIRED_FW_VER_MAJ = 1
REQUIRED_FW_VER_MIN = 73
REQUIRED_FW_VER_MIN = 74
RECONNECT_WAIT = 5
MAX_SUBINTERFACES = 11
def __init__(self, owner, name, port, subint_config, id_interval = None, id_callsign = None):
def __init__(self, owner, configuration):
if RNS.vendor.platformutils.is_android():
raise SystemError("Invalid interface type. The Android-specific RNode interface must be used on Android")
@@ -184,6 +188,75 @@ class RNodeMultiInterface(Interface):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
count = 0
enabled_count = 0
# Count how many interfaces are in the file
for subinterface in c:
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if isinstance(c[subinterface], dict):
count += 1
# Count how many interfaces are enabled to allow for appropriate matrix sizing
for subinterface in c:
if isinstance(c[subinterface], dict):
subinterface_config = c[subinterface]
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
enabled_count += 1
# Create an array with a row for each subinterface
subint_config = [[0 for x in range(11)] for y in range(enabled_count)]
subint_index = 0
for subinterface in c:
if isinstance(c[subinterface], dict):
subinterface_config = c[subinterface]
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
subint_config[subint_index][0] = subinterface
subint_vport = subinterface_config["vport"] if "vport" in subinterface_config else None
subint_config[subint_index][1] = subint_vport
frequency = int(subinterface_config["frequency"]) if "frequency" in subinterface_config else None
subint_config[subint_index][2] = frequency
bandwidth = int(subinterface_config["bandwidth"]) if "bandwidth" in subinterface_config else None
subint_config[subint_index][3] = bandwidth
txpower = int(subinterface_config["txpower"]) if "txpower" in subinterface_config else None
subint_config[subint_index][4] = txpower
spreadingfactor = int(subinterface_config["spreadingfactor"]) if "spreadingfactor" in subinterface_config else None
subint_config[subint_index][5] = spreadingfactor
codingrate = int(subinterface_config["codingrate"]) if "codingrate" in subinterface_config else None
subint_config[subint_index][6] = codingrate
flow_control = subinterface_config.as_bool("flow_control") if "flow_control" in subinterface_config else False
subint_config[subint_index][7] = flow_control
st_alock = float(subinterface_config["airtime_limit_short"]) if "airtime_limit_short" in subinterface_config else None
subint_config[subint_index][8] = st_alock
lt_alock = float(subinterface_config["airtime_limit_long"]) if "airtime_limit_long" in subinterface_config else None
subint_config[subint_index][9] = lt_alock
if "outgoing" in subinterface_config and subinterface_config.as_bool("outgoing") == False:
subint_config[subint_index][10] = False
else:
subint_config[subint_index][10] = True
subint_index += 1
# if no subinterfaces are defined
if count == 0:
raise ValueError("No subinterfaces configured for "+name)
# if no subinterfaces are enabled
elif enabled_count == 0:
raise ValueError("No subinterfaces enabled for "+name)
id_interval = int(c["id_interval"]) if "id_interval" in c else None
id_callsign = c["id_callsign"] if "id_callsign" in c else None
port = c["port"] if "port" in c else None
if port == None:
raise ValueError("No port specified for "+name)
self.HW_MTU = 508
self.clients = 0
@@ -247,6 +320,7 @@ class RNodeMultiInterface(Interface):
if (not self.validcfg):
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
def start(self):
try:
self.open_port()
@@ -295,7 +369,7 @@ class RNodeMultiInterface(Interface):
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
self.serial.close()
else:
if self.platform == KISS.PLATFORM_ESP32:
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
self.display = True
RNS.log("Serial port "+self.port+" is now open")
@@ -321,8 +395,8 @@ class RNodeMultiInterface(Interface):
lt_alock=subint[9]
)
interface.OUT = self.OUT
interface.IN = self.IN
interface.OUT = subint[10]
interface.IN = True
interface.announce_rate_target = self.announce_rate_target
interface.mode = self.mode
@@ -490,7 +564,7 @@ class RNodeMultiInterface(Interface):
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/Reticulum/RNS/Utilities/rnodeconf.py")
RNS.panic()
def processOutgoing(self, data, interface = None):
def process_outgoing(self, data, interface = None):
if interface is None:
# do nothing if RNS tries to transmit on this interface directly
pass
@@ -538,7 +612,7 @@ class RNodeMultiInterface(Interface):
command == KISS.CMD_INT10_DATA or
command == KISS.CMD_INT11_DATA)):
in_frame = False
self.subinterfaces[KISS.int_data_cmd_to_index(command)].processIncoming(data_buffer)
self.subinterfaces[KISS.int_data_cmd_to_index(command)].process_incoming(data_buffer)
self.selected_index = KISS.int_data_cmd_to_index(command)
data_buffer = b""
command_buffer = b""
@@ -817,7 +891,7 @@ class RNodeMultiInterface(Interface):
for interface in self.subinterfaces:
if interface != 0 and interface.online:
interface_available = True
self.subinterfaces[interface.index].processOutgoing(self.id_callsign)
self.subinterfaces[interface.index].process_outgoing(self.id_callsign)
if interface_available:
RNS.log("Interface "+str(self)+" is transmitting beacon data on all subinterfaces: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
@@ -915,46 +989,45 @@ class RNodeSubInterface(Interface):
super().__init__()
match index:
case 0:
sel_cmd = KISS.CMD_SEL_INT0
data_cmd= KISS.CMD_INT0_DATA
case 1:
sel_cmd = KISS.CMD_SEL_INT1
data_cmd= KISS.CMD_INT1_DATA
case 2:
sel_cmd = KISS.CMD_SEL_INT2
data_cmd= KISS.CMD_INT2_DATA
case 3:
sel_cmd = KISS.CMD_SEL_INT3
data_cmd= KISS.CMD_INT3_DATA
case 4:
sel_cmd = KISS.CMD_SEL_INT4
data_cmd= KISS.CMD_INT4_DATA
case 5:
sel_cmd = KISS.CMD_SEL_INT5
data_cmd= KISS.CMD_INT5_DATA
case 6:
sel_cmd = KISS.CMD_SEL_INT6
data_cmd= KISS.CMD_INT6_DATA
case 7:
sel_cmd = KISS.CMD_SEL_INT7
data_cmd= KISS.CMD_INT7_DATA
case 8:
sel_cmd = KISS.CMD_SEL_INT8
data_cmd= KISS.CMD_INT8_DATA
case 9:
sel_cmd = KISS.CMD_SEL_INT9
data_cmd= KISS.CMD_INT9_DATA
case 10:
sel_cmd = KISS.CMD_SEL_INT10
data_cmd= KISS.CMD_INT10_DATA
case 11:
sel_cmd = KISS.CMD_SEL_INT11
data_cmd= KISS.CMD_INT11_DATA
case _:
sel_cmd = KISS.CMD_SEL_INT0
data_cmd= KISS.CMD_INT0_DATA
if index == 0:
sel_cmd = KISS.CMD_SEL_INT0
data_cmd= KISS.CMD_INT0_DATA
elif index == 1:
sel_cmd = KISS.CMD_SEL_INT1
data_cmd= KISS.CMD_INT1_DATA
elif index == 2:
sel_cmd = KISS.CMD_SEL_INT2
data_cmd= KISS.CMD_INT2_DATA
elif index == 3:
sel_cmd = KISS.CMD_SEL_INT3
data_cmd= KISS.CMD_INT3_DATA
elif index == 4:
sel_cmd = KISS.CMD_SEL_INT4
data_cmd= KISS.CMD_INT4_DATA
elif index == 5:
sel_cmd = KISS.CMD_SEL_INT5
data_cmd= KISS.CMD_INT5_DATA
elif index == 6:
sel_cmd = KISS.CMD_SEL_INT6
data_cmd= KISS.CMD_INT6_DATA
elif index == 7:
sel_cmd = KISS.CMD_SEL_INT7
data_cmd= KISS.CMD_INT7_DATA
elif index == 8:
sel_cmd = KISS.CMD_SEL_INT8
data_cmd= KISS.CMD_INT8_DATA
elif index == 9:
sel_cmd = KISS.CMD_SEL_INT9
data_cmd= KISS.CMD_INT9_DATA
elif index == 10:
sel_cmd = KISS.CMD_SEL_INT10
data_cmd= KISS.CMD_INT10_DATA
elif index == 11:
sel_cmd = KISS.CMD_SEL_INT11
data_cmd= KISS.CMD_INT11_DATA
else:
sel_cmd = KISS.CMD_SEL_INT0
data_cmd= KISS.CMD_INT0_DATA
self.owner = owner
self.name = name
@@ -1005,6 +1078,11 @@ class RNodeSubInterface(Interface):
self.parent_interface = parent_interface
self.announce_rate_target = None
self.mode = None
self.announce_cap = None
self.bitrate = None
self.ifac_size = None
# add this interface to the subinterfaces array
self.parent_interface.subinterfaces[index] = self
@@ -1119,13 +1197,13 @@ class RNodeSubInterface(Interface):
except:
self.bitrate = 0
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
self.r_stat_rssi = None
self.r_stat_snr = None
def processOutgoing(self,data):
def process_outgoing(self,data):
if self.online:
if self.interface_ready:
if self.flow_control:
@@ -1137,7 +1215,7 @@ class RNodeSubInterface(Interface):
if self.parent_interface.first_tx == None:
self.parent_interface.first_tx = time.time()
self.txb += len(data)
self.parent_interface.processOutgoing(data, self)
self.parent_interface.process_outgoing(data, self)
else:
self.queue(data)
@@ -1149,7 +1227,7 @@ class RNodeSubInterface(Interface):
if len(self.packet_queue) > 0:
data = self.packet_queue.pop(0)
self.interface_ready = True
self.processOutgoing(data)
self.process_outgoing(data)
elif len(self.packet_queue) == 0:
self.interface_ready = True
+17 -5
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
from time import sleep
import sys
import threading
@@ -42,6 +42,7 @@ class HDLC():
class SerialInterface(Interface):
MAX_CHUNK = 32768
DEFAULT_IFAC_SIZE = 8
owner = None
port = None
@@ -51,7 +52,7 @@ class SerialInterface(Interface):
stopbits = None
serial = None
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
def __init__(self, owner, configuration):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
@@ -62,6 +63,17 @@ class SerialInterface(Interface):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
if port == None:
raise ValueError("No port specified for serial interface")
self.HW_MTU = 564
self.pyserial = serial
@@ -121,12 +133,12 @@ class SerialInterface(Interface):
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
def process_outgoing(self,data):
if self.online:
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
written = self.serial.write(data)
@@ -149,7 +161,7 @@ class SerialInterface(Interface):
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
+170 -73
View File
@@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
import socketserver
import threading
import platform
@@ -30,6 +30,9 @@ import sys
import os
import RNS
class TCPInterface():
HW_MTU = 262144
class HDLC():
FLAG = 0x7E
ESC = 0x7D
@@ -58,8 +61,13 @@ class KISS():
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ThreadingTCP6Server(socketserver.ThreadingMixIn, socketserver.TCPServer):
address_family = socket.AF_INET6
class TCPClientInterface(Interface):
BITRATE_GUESS = 10*1000*1000
DEFAULT_IFAC_SIZE = 16
AUTOCONFIGURE_MTU = True
RECONNECT_WAIT = 5
RECONNECT_MAX_TRIES = None
@@ -78,11 +86,21 @@ class TCPClientInterface(Interface):
I2P_PROBE_INTERVAL = 9
I2P_PROBES = 5
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False, connect_timeout = None):
def __init__(self, owner, configuration, connected_socket=None):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
target_ip = c["target_host"] if "target_host" in c and c["target_host"] != None else None
target_port = int(c["target_port"]) if "target_port" in c and c["target_host"] != None else None
kiss_framing = False
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
kiss_framing = True
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
connect_timeout = c.as_int("connect_timeout") if "connect_timeout" in c else None
max_reconnect_tries = c.as_int("max_reconnect_tries") if "max_reconnect_tries" in c else None
self.HW_MTU = 1064
self.HW_MTU = TCPInterface.HW_MTU
self.IN = True
self.OUT = False
self.socket = None
@@ -177,19 +195,21 @@ class TCPClientInterface(Interface):
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
def detach(self):
self.online = False
if self.socket != None:
if hasattr(self.socket, "close"):
if callable(self.socket.close):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.detached = True
try:
self.socket.shutdown(socket.SHUT_RDWR)
if self.socket != None:
self.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
self.socket.close()
if self.socket != None:
self.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
@@ -200,10 +220,14 @@ class TCPClientInterface(Interface):
if initial:
RNS.log("Establishing TCP connection for "+str(self)+"...", RNS.LOG_DEBUG)
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
address_info = socket.getaddrinfo(self.target_ip, self.target_port, proto=socket.IPPROTO_TCP)[0]
address_family = address_info[0]
target_address = address_info[4]
self.socket = socket.socket(address_family, socket.SOCK_STREAM)
self.socket.settimeout(TCPClientInterface.INITIAL_CONNECT_TIMEOUT)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.socket.connect((self.target_ip, self.target_port))
self.socket.connect(target_address)
self.socket.settimeout(None)
self.online = True
@@ -265,15 +289,16 @@ class TCPClientInterface(Interface):
RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
raise IOError("Attempt to reconnect on a non-initiator TCP interface")
def processIncoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def process_incoming(self, data):
if self.online and not self.detached:
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self, data):
if self.online:
def process_outgoing(self, data):
if self.online and not self.detached:
# while self.writing:
# time.sleep(0.01)
@@ -301,22 +326,22 @@ class TCPClientInterface(Interface):
try:
in_frame = False
escape = False
frame_buffer = b""
data_in = b""
data_buffer = b""
command = KISS.CMD_UNKNOWN
while True:
data_in = self.socket.recv(4096)
if len(data_in) > 0:
pointer = 0
while pointer < len(data_in):
byte = data_in[pointer]
pointer += 1
if self.kiss_framing:
# Read loop for KISS framing
if self.kiss_framing:
# Read loop for KISS framing
pointer = 0
while pointer < len(data_in):
byte = data_in[pointer]
pointer += 1
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
self.process_incoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
@@ -339,25 +364,26 @@ class TCPClientInterface(Interface):
escape = False
data_buffer = data_buffer+bytes([byte])
else:
# Read loop for HDLC framing
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
# Read loop for standard HDLC framing
frame_buffer += data_in
flags_remaining = True
while flags_remaining:
frame_start = frame_buffer.find(HDLC.FLAG)
if frame_start != -1:
frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
if frame_end != -1:
frame = frame_buffer[frame_start+1:frame_end]
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
self.process_incoming(frame)
frame_buffer = frame_buffer[frame_end:]
else:
if (escape):
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
byte = HDLC.FLAG
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
byte = HDLC.ESC
escape = False
data_buffer = data_buffer+bytes([byte])
flags_remaining = False
else:
flags_remaining = False
else:
self.online = False
if self.initiator and not self.detached:
@@ -394,7 +420,8 @@ class TCPClientInterface(Interface):
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
@@ -402,31 +429,73 @@ class TCPClientInterface(Interface):
def __str__(self):
return "TCPInterface["+str(self.name)+"/"+str(self.target_ip)+":"+str(self.target_port)+"]"
if ":" in self.target_ip:
ip_str = f"[{self.target_ip}]"
else:
ip_str = f"{self.target_ip}"
return "TCPInterface["+str(self.name)+"/"+ip_str+":"+str(self.target_port)+"]"
class TCPServerInterface(Interface):
BITRATE_GUESS = 10*1000*1000
BITRATE_GUESS = 10_000_000
DEFAULT_IFAC_SIZE = 16
AUTOCONFIGURE_MTU = True
@staticmethod
def get_address_for_if(name):
def get_address_for_if(name, bind_port, prefer_ipv6=False):
import RNS.vendor.ifaddr.niwrapper as netinfo
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["addr"]
if len(ifaddr) < 1:
raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
if (prefer_ipv6 or not netinfo.AF_INET in ifaddr) and netinfo.AF_INET6 in ifaddr:
bind_ip = ifaddr[netinfo.AF_INET6][0]["addr"]
if bind_ip.lower().startswith("fe80::"):
# We'll need to add the interface as scope for link-local addresses
return TCPServerInterface.get_address_for_host(f"{bind_ip}%{name}", bind_port)
else:
return TCPServerInterface.get_address_for_host(bind_ip, bind_port)
elif netinfo.AF_INET in ifaddr:
bind_ip = ifaddr[netinfo.AF_INET][0]["addr"]
return (bind_ip, bind_port)
else:
raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
@staticmethod
def get_broadcast_for_if(name):
import RNS.vendor.ifaddr.niwrapper as netinfo
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["broadcast"]
def get_address_for_host(name, bind_port):
address_info = socket.getaddrinfo(name, bind_port, proto=socket.IPPROTO_TCP)[0]
if address_info[0] == socket.AF_INET6:
return (name, bind_port, address_info[4][2], address_info[4][3])
elif address_info[0] == socket.AF_INET:
return (name, bind_port)
else:
raise SystemError(f"No suitable kernel interface available for address \"{name}\" for TCPServerInterface to bind to")
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
@property
def clients(self):
return len(self.spawned_interfaces)
def __init__(self, owner, configuration):
super().__init__()
self.HW_MTU = 1064
c = Interface.get_config_obj(configuration)
name = c["name"]
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
bindip = c["listen_ip"] if "listen_ip" in c else None
bindport = int(c["listen_port"]) if "listen_port" in c else None
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
prefer_ipv6 = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
if port != None:
bindport = port
self.HW_MTU = TCPInterface.HW_MTU
self.online = False
self.clients = 0
self.spawned_interfaces = []
self.IN = True
self.OUT = False
@@ -436,24 +505,41 @@ class TCPServerInterface(Interface):
self.i2p_tunneled = i2p_tunneled
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if device != None:
bindip = TCPServerInterface.get_address_for_if(device)
if (bindip != None and bindport != None):
self.receives = True
self.bind_ip = bindip
if bindport == None:
raise SystemError(f"No TCP port configured for interface \"{name}\"")
else:
self.bind_port = bindport
bind_address = None
if device != None:
bind_address = TCPServerInterface.get_address_for_if(device, self.bind_port, prefer_ipv6)
else:
if bindip == None:
raise SystemError(f"No TCP bind IP configured for interface \"{name}\"")
bind_address = TCPServerInterface.get_address_for_host(bindip, self.bind_port)
if bind_address != None:
self.receives = True
self.bind_ip = bind_address[0]
def handlerFactory(callback):
def createHandler(*args, **keys):
return TCPInterfaceHandler(callback, *args, **keys)
return createHandler
self.owner = owner
address = (self.bind_ip, self.bind_port)
ThreadingTCPServer.allow_reuse_address = True
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
if len(bind_address) == 4:
try:
ThreadingTCP6Server.allow_reuse_address = True
self.server = ThreadingTCP6Server(bind_address, handlerFactory(self.incoming_connection))
except Exception as e:
RNS.log(f"Error while binding IPv6 socket for interface, the contained exception was: {e}", RNS.LOG_ERROR)
raise SystemError("Could not bind IPv6 socket for interface. Please check the specified \"listen_ip\" configuration option")
else:
ThreadingTCPServer.allow_reuse_address = True
self.server = ThreadingTCPServer(bind_address, handlerFactory(self.incoming_connection))
self.server.daemon_threads = True
self.bitrate = TCPServerInterface.BITRATE_GUESS
@@ -463,17 +549,20 @@ class TCPServerInterface(Interface):
self.online = True
else:
raise SystemError("Insufficient parameters to create TCP listener")
def incoming_connection(self, handler):
RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
interface_name = "Client on "+self.name
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request, i2p_tunneled=self.i2p_tunneled)
spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None, "i2p_tunneled": self.i2p_tunneled}
spawned_interface = TCPClientInterface(self.owner, spawned_configuration, connected_socket=handler.request)
spawned_interface.OUT = self.OUT
spawned_interface.IN = self.IN
spawned_interface.target_ip = handler.client_address[0]
spawned_interface.target_port = str(handler.client_address[1])
spawned_interface.parent_interface = self
spawned_interface.bitrate = self.bitrate
spawned_interface.optimise_mtu()
spawned_interface.ifac_size = self.ifac_size
spawned_interface.ifac_netname = self.ifac_netname
@@ -503,7 +592,9 @@ class TCPServerInterface(Interface):
spawned_interface.online = True
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
self.clients += 1
while spawned_interface in self.spawned_interfaces:
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
spawned_interface.read_loop()
def received_announce(self, from_spawned=False):
@@ -512,18 +603,19 @@ class TCPServerInterface(Interface):
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def processOutgoing(self, data):
def process_outgoing(self, data):
pass
def detach(self):
self.detached = True
self.online = False
if self.server != None:
if hasattr(self.server, "shutdown"):
if callable(self.server.shutdown):
try:
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.server.shutdown()
self.detached = True
self.server.server_close()
self.server = None
except Exception as e:
@@ -531,7 +623,12 @@ class TCPServerInterface(Interface):
def __str__(self):
return "TCPServerInterface["+self.name+"/"+self.bind_ip+":"+str(self.bind_port)+"]"
if ":" in self.bind_ip:
ip_str = f"[{self.bind_ip}]"
else:
ip_str = f"{self.bind_ip}"
return "TCPServerInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
class TCPInterfaceHandler(socketserver.BaseRequestHandler):
+21 -5
View File
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .Interface import Interface
from RNS.Interfaces.Interface import Interface
import socketserver
import threading
import socket
@@ -31,6 +31,7 @@ import RNS
class UDPInterface(Interface):
BITRATE_GUESS = 10*1000*1000
DEFAULT_IFAC_SIZE = 16
@staticmethod
def get_address_for_if(name):
@@ -44,9 +45,24 @@ class UDPInterface(Interface):
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["broadcast"]
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
def __init__(self, owner, configuration):
super().__init__()
c = Interface.get_config_obj(configuration)
name = c["name"]
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
bindip = c["listen_ip"] if "listen_ip" in c else None
bindport = int(c["listen_port"]) if "listen_port" in c else None
forwardip = c["forward_ip"] if "forward_ip" in c else None
forwardport = int(c["forward_port"]) if "forward_port" in c else None
if port != None:
if bindport == None:
bindport = port
if forwardport == None:
forwardport = port
self.HW_MTU = 1064
self.IN = True
@@ -75,7 +91,7 @@ class UDPInterface(Interface):
self.owner = owner
address = (self.bind_ip, self.bind_port)
socketserver.UDPServer.address_family = socket.AF_INET
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
self.server = socketserver.UDPServer(address, handlerFactory(self.process_incoming))
thread = threading.Thread(target=self.server.serve_forever)
thread.daemon = True
@@ -89,11 +105,11 @@ class UDPInterface(Interface):
self.forward_port = forwardport
def processIncoming(self, data):
def process_incoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
def process_outgoing(self,data):
try:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+4 -2
View File
@@ -24,5 +24,7 @@ import os
import glob
import RNS.Interfaces.Android
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
+145 -38
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
@@ -21,13 +21,14 @@
# SOFTWARE.
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
from RNS.Cryptography import Fernet
from RNS.Cryptography import Token
from RNS.Channel import Channel, LinkChannelOutlet
from time import sleep
from .vendor import umsgpack as umsgpack
import threading
import inspect
import struct
import math
import time
import RNS
@@ -61,14 +62,16 @@ class Link:
ECPUBSIZE = 32+32
KEYSIZE = 32
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.TOKEN_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
ESTABLISHMENT_TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
"""
Timeout for link establishment in seconds per hop to destination.
"""
TRAFFIC_TIMEOUT_FACTOR = 6
LINK_MTU_SIZE = 3
TRAFFIC_TIMEOUT_MIN_MS = 5
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_TIMEOUT_FACTOR = 4
"""
RTT timeout factor used in link timeout calculation.
@@ -105,16 +108,46 @@ class Link:
ACCEPT_ALL = 0x02
resource_strategies = [ACCEPT_NONE, ACCEPT_APP, ACCEPT_ALL]
@staticmethod
def mtu_bytes(mtu):
return struct.pack(">I", mtu & 0xFFFFFF)[1:]
@staticmethod
def mtu_from_lr_packet(packet):
if len(packet.data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
return (packet.data[Link.ECPUBSIZE] << 16) + (packet.data[Link.ECPUBSIZE+1] << 8) + (packet.data[Link.ECPUBSIZE+2])
else:
return None
@staticmethod
def mtu_from_lp_packet(packet):
if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
mtu_bytes = packet.data[RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE]
return (mtu_bytes[0] << 16) + (mtu_bytes[1] << 8) + (mtu_bytes[2])
else:
return None
@staticmethod
def validate_request(owner, data, packet):
if len(data) == (Link.ECPUBSIZE):
if len(data) == Link.ECPUBSIZE or len(data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
try:
link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE])
link.set_link_id(packet)
if len(data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
RNS.log("Link request includes MTU signalling", RNS.LOG_DEBUG) # TODO: Remove debug
try:
link.mtu = Link.mtu_from_lr_packet(packet) or Reticulum.MTU
except Exception as e:
RNS.trace_exception(e)
link.mtu = RNS.Reticulum.MTU
link.update_mdu()
link.destination = packet.destination
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops) + Link.KEEPALIVE
link.establishment_cost += len(packet.raw)
RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE)
RNS.log(f"Validating link request {RNS.prettyhexrep(link.link_id)}", RNS.LOG_DEBUG)
RNS.log(f"Link MTU configured to {RNS.prettysize(link.mtu)}", RNS.LOG_EXTREME)
RNS.log(f"Establishment timeout is {RNS.prettytime(link.establishment_timeout)} for incoming link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_EXTREME)
link.handshake()
link.attached_interface = packet.receiving_interface
@@ -122,18 +155,18 @@ class Link:
link.request_time = time.time()
RNS.Transport.register_link(link)
link.last_inbound = time.time()
link.__update_phy_stats(packet, force_update=True)
link.start_watchdog()
RNS.log("Incoming link request "+str(link)+" accepted", RNS.LOG_DEBUG)
RNS.log("Incoming link request "+str(link)+" accepted on "+str(link.attached_interface), RNS.LOG_DEBUG)
return link
except Exception as e:
RNS.log("Validating link request failed", RNS.LOG_VERBOSE)
RNS.log("exc: "+str(e))
RNS.log(f"Validating link request failed: {e}", RNS.LOG_VERBOSE)
return None
else:
RNS.log("Invalid link request payload size, dropping request", RNS.LOG_DEBUG)
RNS.log(f"Invalid link request payload size of {len(data)} bytes, dropping request", RNS.LOG_DEBUG)
return None
@@ -141,9 +174,13 @@ class Link:
if destination != None and destination.type != RNS.Destination.SINGLE:
raise TypeError("Links can only be established to the \"single\" destination type")
self.rtt = None
self.mtu = RNS.Reticulum.MTU
self.establishment_cost = 0
self.establishment_rate = None
self.callbacks = LinkCallbacks()
self.resource_strategy = Link.ACCEPT_NONE
self.last_resource_window = None
self.last_resource_eifr = None
self.outgoing_resources = []
self.incoming_resources = []
self.pending_requests = []
@@ -186,7 +223,7 @@ class Link:
self.prv = X25519PrivateKey.generate()
self.sig_prv = Ed25519PrivateKey.generate()
self.fernet = None
self.token = None
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes()
@@ -206,8 +243,13 @@ class Link:
if closed_callback != None:
self.set_link_closed_callback(closed_callback)
if (self.initiator):
self.request_data = self.pub_bytes+self.sig_pub_bytes
if self.initiator:
link_mtu = b""
nh_hw_mtu = RNS.Transport.next_hop_interface_hw_mtu(destination.hash)
if RNS.Reticulum.link_mtu_discovery() and nh_hw_mtu:
link_mtu = Link.mtu_bytes(nh_hw_mtu)
RNS.log(f"Signalling link MTU of {RNS.prettysize(nh_hw_mtu)} for link", RNS.LOG_DEBUG) # TODO: Remove debug
self.request_data = self.pub_bytes+self.sig_pub_bytes+link_mtu
self.packet = RNS.Packet(destination, self.request_data, packet_type=RNS.Packet.LINKREQUEST)
self.packet.pack()
self.establishment_cost += len(self.packet.raw)
@@ -231,8 +273,17 @@ class Link:
if not hasattr(self.peer_pub, "curve"):
self.peer_pub.curve = Link.CURVE
@staticmethod
def link_id_from_lr_packet(packet):
hashable_part = packet.get_hashable_part()
if len(packet.data) > Link.ECPUBSIZE:
diff = len(packet.data) - Link.ECPUBSIZE
hashable_part = hashable_part[:-diff]
return RNS.Identity.truncated_hash(hashable_part)
def set_link_id(self, packet):
self.link_id = packet.getTruncatedHash()
self.link_id = Link.link_id_from_lr_packet(packet)
self.hash = self.link_id
def handshake(self):
@@ -251,10 +302,14 @@ class Link:
def prove(self):
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes
mtu_bytes = b""
if self.mtu != RNS.Reticulum.MTU:
mtu_bytes = Link.mtu_bytes(self.mtu)
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes+mtu_bytes
signature = self.owner.identity.sign(signed_data)
proof_data = signature+self.pub_bytes
proof_data = signature+self.pub_bytes+mtu_bytes
proof = RNS.Packet(self, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.LRPROOF)
proof.send()
self.establishment_cost += len(proof.raw)
@@ -277,6 +332,14 @@ class Link:
def validate_proof(self, packet):
try:
if self.status == Link.PENDING:
mtu_bytes = b""
confirmed_mtu = None
if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
confirmed_mtu = Link.mtu_from_lp_packet(packet)
mtu_bytes = Link.mtu_bytes(confirmed_mtu)
packet.data = packet.data[:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2]
RNS.log(f"Destination confirmed link MTU of {RNS.prettysize(confirmed_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2:
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2]
peer_sig_pub_bytes = self.destination.identity.get_public_key()[Link.ECPUBSIZE//2:Link.ECPUBSIZE]
@@ -284,7 +347,7 @@ class Link:
self.handshake()
self.establishment_cost += len(packet.raw)
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes+mtu_bytes
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
if self.destination.identity.validate(signature, signed_data):
@@ -294,11 +357,13 @@ class Link:
self.rtt = time.time() - self.request_time
self.attached_interface = packet.receiving_interface
self.__remote_identity = self.destination.identity
self.mtu = confirmed_mtu or RNS.Reticulum.MTU
self.update_mdu()
self.status = Link.ACTIVE
self.activated_at = time.time()
self.last_proof = self.activated_at
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_DEBUG)
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
@@ -307,6 +372,7 @@ class Link:
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
rtt_packet.send()
self.had_outbound()
self.__update_phy_stats(packet)
if self.callbacks.link_established != None:
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
@@ -358,7 +424,7 @@ class Link:
if timeout == None:
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME*1.125
if len(packed_request) <= Link.MDU:
if len(packed_request) <= self.mdu:
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
packet_receipt = request_packet.send()
@@ -392,6 +458,10 @@ class Link:
)
def update_mdu(self):
self.mdu = self.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
self.mdu = math.floor((self.mtu-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.TOKEN_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
def rtt_packet(self, packet):
try:
measured_rtt = time.time() - self.request_time
@@ -433,19 +503,28 @@ class Link:
"""
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
"""
return self.rssi
if self.__track_phy_stats:
return self.rssi
else:
return None
def get_snr(self):
"""
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
"""
return self.rssi
if self.__track_phy_stats:
return self.snr
else:
return None
def get_q(self):
"""
:returns: The physical layer *Link Quality* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
"""
return self.rssi
if self.__track_phy_stats:
return self.q
else:
return None
def get_establishment_rate(self):
"""
@@ -638,9 +717,14 @@ class Link:
sleep(sleep_time)
if not self.__track_phy_stats:
self.rssi = None
self.snr = None
self.q = None
def __update_phy_stats(self, packet, query_shared = True):
if self.__track_phy_stats:
def __update_phy_stats(self, packet, query_shared = True, force_update = False):
if self.__track_phy_stats or force_update:
if query_shared:
reticulum = RNS.Reticulum.get_instance()
if packet.rssi == None: packet.rssi = reticulum.get_packet_rssi(packet.packet_hash)
@@ -692,7 +776,7 @@ class Link:
if response != None:
packed_response = umsgpack.packb([request_id, response])
if len(packed_response) <= Link.MDU:
if len(packed_response) <= self.mdu:
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
else:
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
@@ -760,7 +844,7 @@ class Link:
self.watchdog_lock = True
if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])):
if packet.receiving_interface != self.attached_interface:
RNS.log("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
RNS.log(f"Link-associated packet received on unexpected interface {packet.receiving_interface} instead of {self.attached_interface}! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
else:
self.last_inbound = time.time()
if packet.context != RNS.Packet.KEEPALIVE:
@@ -774,7 +858,10 @@ class Link:
should_query = False
if packet.context == RNS.Packet.NONE:
plaintext = self.decrypt(packet.data)
packet.ratchet_id = self.link_id
if plaintext != None:
self.__update_phy_stats(packet, query_shared=True)
if self.callbacks.packet != None:
thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet))
thread.daemon = True
@@ -782,19 +869,15 @@ class Link:
if self.destination.proof_strategy == RNS.Destination.PROVE_ALL:
packet.prove()
should_query = True
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
if self.destination.callbacks.proof_requested:
try:
if self.destination.callbacks.proof_requested(packet):
packet.prove()
should_query = True
except Exception as e:
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
self.__update_phy_stats(packet, query_shared=should_query)
elif packet.context == RNS.Packet.LINKIDENTIFY:
plaintext = self.decrypt(packet.data)
if plaintext != None:
@@ -879,6 +962,8 @@ class Link:
resource_advertisement.link = self
if self.callbacks.resource(resource_advertisement):
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
else:
RNS.Resource.reject(packet)
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:
@@ -900,6 +985,11 @@ class Link:
if not packet.packet_hash in resource.req_hashlist:
resource.req_hashlist.append(packet.packet_hash)
resource.request(plaintext)
# TODO: Test and possibly enable this at some point
# def request_job():
# resource.request(plaintext)
# threading.Thread(target=request_job, daemon=True).start()
elif packet.context == RNS.Packet.RESOURCE_HMU:
plaintext = self.decrypt(packet.data)
@@ -919,6 +1009,15 @@ class Link:
if resource_hash == resource.hash:
resource.cancel()
elif packet.context == RNS.Packet.RESOURCE_RCL:
plaintext = self.decrypt(packet.data)
if plaintext != None:
self.__update_phy_stats(packet)
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
for resource in self.outgoing_resources:
if resource_hash == resource.hash:
resource._rejected()
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)
@@ -958,14 +1057,14 @@ class Link:
def encrypt(self, plaintext):
try:
if not self.fernet:
if not self.token:
try:
self.fernet = Fernet(self.derived_key)
self.token = Token(self.derived_key)
except Exception as e:
RNS.log("Could not instantiate Fernet while performin encryption on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Could not instantiate token while performing encryption on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
return self.fernet.encrypt(plaintext)
return self.token.encrypt(plaintext)
except Exception as e:
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -974,10 +1073,10 @@ class Link:
def decrypt(self, ciphertext):
try:
if not self.fernet:
self.fernet = Fernet(self.derived_key)
if not self.token:
self.token = Token(self.derived_key)
return self.fernet.decrypt(ciphertext)
return self.token.decrypt(ciphertext)
except Exception as e:
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -1055,6 +1154,8 @@ class Link:
def resource_concluded(self, resource):
if resource in self.incoming_resources:
self.last_resource_window = resource.window
self.last_resource_eifr = resource.eifr
self.incoming_resources.remove(resource)
if resource in self.outgoing_resources:
self.outgoing_resources.remove(resource)
@@ -1084,6 +1185,12 @@ class Link:
return False
def get_last_resource_window(self):
return self.last_resource_window
def get_last_resource_eifr(self):
return self.last_resource_eifr
def cancel_outgoing_resource(self, resource):
if resource in self.outgoing_resources:
self.outgoing_resources.remove(resource)
+57 -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
@@ -91,7 +95,7 @@ class Packet:
# With an MTU of 500, the maximum of data we can
# send in a single encrypted packet is given by
# the below calculation; 383 bytes.
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.FERNET_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.TOKEN_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
"""
The maximum size of the payload data in a single encrypted packet
"""
@@ -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
@@ -130,9 +137,14 @@ class Packet:
self.fromPacked = True
self.create_receipt = False
self.MTU = RNS.Reticulum.MTU
if destination and destination.type == RNS.Destination.LINK:
self.MTU = destination.mtu
else:
self.MTU = RNS.Reticulum.MTU
self.sent_at = None
self.packet_hash = None
self.ratchet_id = None
self.attached_interface = attached_interface
self.receiving_interface = None
@@ -142,9 +154,10 @@ class Packet:
def get_packed_flags(self):
if self.context == Packet.LRPROOF:
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
else:
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
return packed_flags
def pack(self):
@@ -187,6 +200,8 @@ class Packet:
# In all other cases, we encrypt the packet
# with the destination's encryption method
self.ciphertext = self.destination.encrypt(self.data)
if hasattr(self.destination, "latest_ratchet_id"):
self.ratchet_id = self.destination.latest_ratchet_id
if self.header_type == Packet.HEADER_2:
if self.transport_id != None:
@@ -216,7 +231,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)
@@ -329,6 +345,33 @@ class Packet:
return hashable_part
def get_rssi(self):
"""
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``.
"""
if self.rssi != None:
return self.rssi
else:
return reticulum.get_packet_rssi(self.packet_hash)
def get_snr(self):
"""
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``.
"""
if self.snr != None:
return self.snr
else:
return reticulum.get_packet_snr(self.packet_hash)
def get_q(self):
"""
:returns: The physical layer *Link Quality* if available, otherwise ``None``.
"""
if self.q != None:
return self.q
else:
return reticulum.get_packet_q(self.packet_hash)
class ProofDestination:
def __init__(self, packet):
self.hash = packet.get_hash()[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8];
@@ -369,7 +412,7 @@ class PacketReceipt:
self.proof_packet = None
if packet.destination.type == RNS.Destination.LINK:
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
self.timeout = max(packet.destination.rtt * packet.destination.traffic_timeout_factor, RNS.Link.TRAFFIC_TIMEOUT_MIN_MS/1000)
else:
self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
@@ -409,6 +452,7 @@ class PacketReceipt:
except Exception as e:
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
return True
else:
@@ -440,7 +484,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
@@ -461,6 +505,10 @@ class PacketReceipt:
return False
elif len(proof) == PacketReceipt.IMPL_LENGTH:
# This is an implicit proof
if not hasattr(self.destination, "identity"):
return False
if self.destination.identity == None:
return False
+169 -34
View File
@@ -54,6 +54,9 @@ class Resource:
# The maximum window size for transfers on slow links
WINDOW_MAX_SLOW = 10
# The maximum window size for transfers on very slow links
WINDOW_MAX_VERY_SLOW = 4
# The maximum window size for transfers on fast links
WINDOW_MAX_FAST = 75
@@ -65,12 +68,22 @@ class Resource:
# rounds, the fast link window size will be allowed.
FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2
# If the very slow rate is sustained for this many request
# rounds, window will be capped to the very slow limit.
VERY_SLOW_RATE_THRESHOLD = 2
# If the RTT rate is higher than this value,
# the max window size for fast links will be used.
# The default is 50 Kbps (the value is stored in
# bytes per second, hence the "/ 8").
RATE_FAST = (50*1000) / 8
# If the RTT rate is lower than this value,
# the window size will be capped at .
# The default is 50 Kbps (the value is stored in
# bytes per second, hence the "/ 8").
RATE_VERY_SLOW = (2*1000) / 8
# The minimum allowed flexibility of the window size.
# The difference between window_max and window_min
# will never be smaller than this value.
@@ -105,6 +118,7 @@ class Resource:
PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
PROOF_TIMEOUT_FACTOR = 3
MAX_RETRIES = 16
MAX_ADV_RETRIES = 4
SENDER_GRACE_TIME = 10.0
@@ -127,6 +141,19 @@ class Resource:
COMPLETE = 0x06
FAILED = 0x07
CORRUPT = 0x08
REJECTED = 0x00
@staticmethod
def reject(advertisement_packet):
try:
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
resource_hash = adv.h
reject_packet = RNS.Packet(advertisement_packet.link, resource_hash, context=RNS.Packet.RESOURCE_RCL)
reject_packet.send()
except Exception as e:
RNS.log(f"An error ocurred while rejecting advertised resource: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
@staticmethod
def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
@@ -147,9 +174,9 @@ class Resource:
resource.encrypted = True if resource.flags & 0x01 else False
resource.compressed = True if resource.flags >> 1 & 0x01 else False
resource.initiator = False
resource.callback = callback
resource.callback = callback
resource.__progress_callback = progress_callback
resource.total_parts = int(math.ceil(resource.size/float(Resource.SDU)))
resource.total_parts = int(math.ceil(resource.size/float(resource.sdu)))
resource.received_count = 0
resource.outstanding_parts = 0
resource.parts = [None] * resource.total_parts
@@ -172,6 +199,13 @@ class Resource:
resource.waiting_for_hmu = False
resource.receiving_part = False
resource.consecutive_completed_height = -1
previous_window = resource.link.get_last_resource_window()
previous_eifr = resource.link.get_last_resource_eifr()
if previous_window:
resource.window = previous_window
if previous_eifr:
resource.previous_eifr = previous_eifr
if not resource.link.has_incoming_resource(resource):
resource.link.register_incoming_resource(resource)
@@ -220,7 +254,6 @@ class Resource:
data_size = os.stat(data.name).st_size
self.total_size = data_size
self.grand_total_parts = math.ceil(data_size/Resource.SDU)
if data_size <= Resource.MAX_EFFICIENT_SIZE:
self.total_segments = 1
@@ -241,7 +274,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
@@ -259,6 +291,10 @@ class Resource:
self.status = Resource.NONE
self.link = link
if self.link.mtu:
self.sdu = self.link.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
else:
self.sdu = link.mdu or Resource.SDU
self.max_retries = Resource.MAX_RETRIES
self.max_adv_retries = Resource.MAX_ADV_RETRIES
self.retries_left = self.max_retries
@@ -274,9 +310,14 @@ class Resource:
self.req_sent = 0
self.req_resp_rtt_rate = 0
self.rtt_rxd_bytes_at_part_req = 0
self.req_data_rtt_rate = 0
self.eifr = None
self.previous_eifr = None
self.fast_rate_rounds = 0
self.very_slow_rate_rounds = 0
self.request_id = request_id
self.is_response = is_response
self.auto_compress = auto_compress
self.req_hashlist = []
self.receiver_min_consecutive_height = 0
@@ -293,9 +334,9 @@ class Resource:
compression_began = time.time()
if (auto_compress and len(self.uncompressed_data) <= Resource.AUTO_COMPRESS_MAX_SIZE):
RNS.log("Compressing resource data...", RNS.LOG_DEBUG)
RNS.log("Compressing resource data...", RNS.LOG_EXTREME)
self.compressed_data = bz2.compress(self.uncompressed_data)
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_DEBUG)
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_EXTREME)
else:
self.compressed_data = self.uncompressed_data
@@ -304,7 +345,7 @@ class Resource:
if (self.compressed_size < self.uncompressed_size and auto_compress):
saved_bytes = len(self.uncompressed_data) - len(self.compressed_data)
RNS.log("Compression saved "+str(saved_bytes)+" bytes, sending compressed", RNS.LOG_DEBUG)
RNS.log("Compression saved "+str(saved_bytes)+" bytes, sending compressed", RNS.LOG_EXTREME)
self.data = b""
self.data += RNS.Identity.get_random_hash()[:Resource.RANDOM_HASH_SIZE]
@@ -322,7 +363,7 @@ class Resource:
self.compressed = False
self.compressed_data = None
if auto_compress:
RNS.log("Compression did not decrease size, sending uncompressed", RNS.LOG_DEBUG)
RNS.log("Compression did not decrease size, sending uncompressed", RNS.LOG_EXTREME)
# Resources handle encryption directly to
# make optimal use of packet MTU on an entire
@@ -333,12 +374,13 @@ class Resource:
self.size = len(self.data)
self.sent_parts = 0
hashmap_entries = int(math.ceil(self.size/float(Resource.SDU)))
hashmap_entries = int(math.ceil(self.size/float(self.sdu)))
self.total_parts = hashmap_entries
hashmap_ok = False
while not hashmap_ok:
hashmap_computation_began = time.time()
RNS.log("Starting resource hashmap computation with "+str(hashmap_entries)+" entries...", RNS.LOG_DEBUG)
RNS.log("Starting resource hashmap computation with "+str(hashmap_entries)+" entries...", RNS.LOG_EXTREME)
self.random_hash = RNS.Identity.get_random_hash()[:Resource.RANDOM_HASH_SIZE]
self.hash = RNS.Identity.full_hash(data+self.random_hash)
@@ -354,11 +396,11 @@ class Resource:
self.hashmap = b""
collision_guard_list = []
for i in range(0,hashmap_entries):
data = self.data[i*Resource.SDU:(i+1)*Resource.SDU]
data = self.data[i*self.sdu:(i+1)*self.sdu]
map_hash = self.get_map_hash(data)
if map_hash in collision_guard_list:
RNS.log("Found hash collision in resource map, remapping...", RNS.LOG_VERBOSE)
RNS.log("Found hash collision in resource map, remapping...", RNS.LOG_DEBUG)
hashmap_ok = False
break
else:
@@ -374,7 +416,7 @@ class Resource:
self.hashmap += part.map_hash
self.parts.append(part)
RNS.log("Hashmap computation concluded in "+str(round(time.time()-hashmap_computation_began, 3))+" seconds", RNS.LOG_DEBUG)
RNS.log("Hashmap computation concluded in "+str(round(time.time()-hashmap_computation_began, 3))+" seconds", RNS.LOG_EXTREME)
if advertise:
self.advertise()
@@ -433,7 +475,7 @@ class Resource:
self.status = Resource.ADVERTISED
self.retries_left = self.max_adv_retries
self.link.register_outgoing_resource(self)
RNS.log("Sent resource advertisement for "+RNS.prettyhexrep(self.hash), RNS.LOG_DEBUG)
RNS.log("Sent resource advertisement for "+RNS.prettyhexrep(self.hash), RNS.LOG_EXTREME)
except Exception as e:
RNS.log("Could not advertise resource, the contained exception was: "+str(e), RNS.LOG_ERROR)
self.cancel()
@@ -441,6 +483,22 @@ class Resource:
self.watchdog_job()
def update_eifr(self):
if self.rtt == None:
rtt = self.link.rtt
else:
rtt = self.rtt
if self.req_data_rtt_rate != 0:
expected_inflight_rate = self.req_data_rtt_rate*8
else:
if self.previous_eifr != None:
expected_inflight_rate = self.previous_eifr
else:
expected_inflight_rate = self.link.establishment_cost*8 / rtt
self.eifr = expected_inflight_rate
def watchdog_job(self):
thread = threading.Thread(target=self.__watchdog_job)
thread.daemon = True
@@ -455,7 +513,6 @@ class Resource:
sleep(0.025)
sleep_time = None
if self.status == Resource.ADVERTISED:
sleep_time = (self.adv_sent+self.timeout+Resource.PROCESSING_GRACE)-time.time()
if sleep_time < 0:
@@ -479,17 +536,19 @@ class Resource:
elif self.status == Resource.TRANSFERRING:
if not self.initiator:
if self.rtt == None:
rtt = self.link.rtt
else:
rtt = self.rtt
window_remaining = self.outstanding_parts
retries_used = self.max_retries - self.retries_left
extra_wait = retries_used * Resource.PER_RETRY_DELAY
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
self.update_eifr()
expected_tof_remaining = (self.outstanding_parts*self.sdu*8)/self.eifr
if self.req_resp_rtt_rate != 0:
sleep_time = self.last_activity + self.part_timeout_factor*expected_tof_remaining + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
else:
sleep_time = self.last_activity + self.part_timeout_factor*((3*self.sdu)/self.eifr) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
# RNS.log(f"EIFR {RNS.prettyspeed(self.eifr)}, ETOF {RNS.prettyshorttime(expected_tof_remaining)} ", RNS.LOG_DEBUG, pt=True)
# RNS.log(f"Resource ST {RNS.prettyshorttime(sleep_time)}, RTT {RNS.prettyshorttime(self.rtt or self.link.rtt)}, {self.outstanding_parts} left", RNS.LOG_DEBUG, pt=True)
if sleep_time < 0:
if self.retries_left > 0:
@@ -519,6 +578,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:
@@ -535,8 +598,11 @@ class Resource:
self.last_part_sent = time.time()
sleep_time = 0.001
elif self.status == Resource.REJECTED:
sleep_time = 0.001
if sleep_time == 0:
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_WARNING)
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_DEBUG)
if sleep_time == None or sleep_time < 0:
RNS.log("Timing error, cancelling resource transfer.", RNS.LOG_ERROR)
self.cancel()
@@ -610,6 +676,7 @@ class Resource:
proof_data = self.hash+proof
proof_packet = RNS.Packet(self.link, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
proof_packet.send()
RNS.Transport.cache(proof_packet, force_cache=True)
except Exception as e:
RNS.log("Could not send proof packet, cancelling resource", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
@@ -628,6 +695,7 @@ class Resource:
request_id = self.request_id,
is_response = self.is_response,
advertise = False,
auto_compress = self.auto_compress,
)
def validate_proof(self, proof_data):
@@ -751,6 +819,7 @@ class Resource:
if rtt != 0:
self.req_data_rtt_rate = req_transferred/rtt
self.update_eifr()
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
if self.req_data_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
@@ -759,6 +828,12 @@ class Resource:
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
self.window_max = Resource.WINDOW_MAX_FAST
if self.fast_rate_rounds == 0 and self.req_data_rtt_rate < Resource.RATE_VERY_SLOW and self.very_slow_rate_rounds < Resource.VERY_SLOW_RATE_THRESHOLD:
self.very_slow_rate_rounds += 1
if self.very_slow_rate_rounds == Resource.VERY_SLOW_RATE_THRESHOLD:
self.window_max = Resource.WINDOW_MAX_VERY_SLOW
self.request_next()
else:
self.receiving_part = False
@@ -900,6 +975,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:
@@ -931,6 +1007,18 @@ class Resource:
except Exception as e:
RNS.log("Error while executing callbacks on resource cancel from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def _rejected(self):
if self.status < Resource.COMPLETE:
if self.initiator:
self.status = Resource.REJECTED
self.link.cancel_outgoing_resource(self)
if self.callback != None:
try:
self.link.resource_concluded(self)
self.callback(self)
except Exception as e:
RNS.log("Error while executing callbacks on resource reject from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def set_callback(self, callback):
self.callback = callback
@@ -943,21 +1031,68 @@ class Resource:
"""
if self.status == RNS.Resource.COMPLETE and self.segment_index == self.total_segments:
return 1.0
elif self.initiator:
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
self.processed_parts += self.sent_parts
self.progress_total_parts = float(self.grand_total_parts)
else:
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
self.processed_parts += self.received_count
if self.split:
self.progress_total_parts = float(math.ceil(self.total_size/Resource.SDU))
else:
if not self.split:
self.processed_parts = self.sent_parts
self.progress_total_parts = float(self.total_parts)
else:
is_last_segment = self.segment_index != self.total_segments
total_segments = self.total_segments
processed_segments = self.segment_index-1
current_segment_parts = self.total_parts
max_parts_per_segment = math.ceil(Resource.MAX_EFFICIENT_SIZE/self.sdu)
previously_processed_parts = processed_segments*max_parts_per_segment
if current_segment_parts < max_parts_per_segment:
current_segment_factor = max_parts_per_segment / current_segment_parts
else:
current_segment_factor = 1
self.processed_parts = previously_processed_parts + self.sent_parts*current_segment_factor
self.progress_total_parts = self.total_segments*max_parts_per_segment
else:
if not self.split:
self.processed_parts = self.received_count
self.progress_total_parts = float(self.total_parts)
else:
is_last_segment = self.segment_index != self.total_segments
total_segments = self.total_segments
processed_segments = self.segment_index-1
current_segment_parts = self.total_parts
max_parts_per_segment = math.ceil(Resource.MAX_EFFICIENT_SIZE/self.sdu)
previously_processed_parts = processed_segments*max_parts_per_segment
if current_segment_parts < max_parts_per_segment:
current_segment_factor = max_parts_per_segment / current_segment_parts
else:
current_segment_factor = 1
self.processed_parts = previously_processed_parts + self.received_count*current_segment_factor
self.progress_total_parts = self.total_segments*max_parts_per_segment
progress = min(1.0, self.processed_parts / self.progress_total_parts)
return progress
def get_segment_progress(self):
if self.status == RNS.Resource.COMPLETE and self.segment_index == self.total_segments:
return 1.0
elif self.initiator:
processed_parts = self.sent_parts
else:
processed_parts = self.received_count
progress = min(1.0, processed_parts / self.total_parts)
return progress
def get_transfer_size(self):
"""
:returns: The number of bytes needed to transfer the resource.
+247 -509
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,17 +29,19 @@ if get_platform() == "android":
from .Interfaces import TCPInterface
from .Interfaces import UDPInterface
from .Interfaces import I2PInterface
from .Interfaces import RNodeMultiInterface
from .Interfaces.Android import RNodeInterface
from .Interfaces.Android import SerialInterface
from .Interfaces.Android import KISSInterface
else:
from .Interfaces import *
from RNS.Interfaces import *
from .vendor.configobj import ConfigObj
from RNS.vendor.configobj import ConfigObj
import configparser
import multiprocessing.connection
import signal
import importlib
import threading
import signal
import atexit
import struct
import array
@@ -88,6 +90,16 @@ class Reticulum:
the default value.
"""
LINK_MTU_DISCOVERY = False
"""
Whether automatic link MTU discovery is enabled by default in this
release. Link MTU discovery significantly increases throughput over
fast links, but requires all intermediary hops to also support it.
Support for this feature was added in RNS version 0.9.0. This option
will become enabled by default in the near future. Please update your
RNS instances.
"""
MAX_QUEUED_ANNOUNCES = 16384
QUEUED_ANNOUNCE_LIFE = 60*60*24
@@ -147,31 +159,41 @@ class Reticulum:
configpath = ""
storagepath = ""
cachepath = ""
interfacepath = ""
__instance = None
__interface_detach_ran = False
__exit_handler_ran = False
@staticmethod
def exit_handler():
# This exit handler is called whenever Reticulum is asked to
# shut down, and will in turn call exit handlers in other
# classes, saving necessary information to disk and carrying
# out cleanup operations.
if not Reticulum.__exit_handler_ran:
if not Reticulum.__interface_detach_ran:
RNS.Transport.detach_interfaces()
RNS.Transport.exit_handler()
RNS.Identity.exit_handler()
RNS.Transport.exit_handler()
RNS.Identity.exit_handler()
if RNS.Profiler.ran():
RNS.Profiler.results()
RNS.loglevel = -1
@staticmethod
def sigint_handler(signal, frame):
RNS.Transport.detach_interfaces()
Reticulum.__interface_detach_ran = True
RNS.exit()
@staticmethod
def sigterm_handler(signal, frame):
RNS.Transport.detach_interfaces()
Reticulum.__interface_detach_ran = True
RNS.exit()
@staticmethod
def get_instance():
"""
@@ -179,7 +201,7 @@ class Reticulum:
"""
return Reticulum.__instance
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None, require_shared_instance=False):
"""
Initialises and starts a Reticulum instance. This must be
done before any other operations, and Reticulum will not
@@ -208,14 +230,19 @@ class Reticulum:
if logdest == RNS.LOG_FILE:
RNS.logdest = RNS.LOG_FILE
RNS.logfile = Reticulum.configdir+"/logfile"
elif callable(logdest):
RNS.logdest = RNS.LOG_CALLBACK
RNS.logcall = logdest
Reticulum.configpath = Reticulum.configdir+"/config"
Reticulum.storagepath = Reticulum.configdir+"/storage"
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
Reticulum.__transport_enabled = False
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
Reticulum.__remote_management_enabled = False
Reticulum.__use_implicit_proof = True
Reticulum.__allow_probes = False
@@ -241,6 +268,8 @@ class Reticulum:
RNS.loglevel = self.requested_loglevel
self.is_shared_instance = False
self.shared_instance_interface = None
self.require_shared = require_shared_instance
self.is_connected_to_shared_instance = False
self.is_standalone_instance = False
self.jobs_thread = None
@@ -259,6 +288,9 @@ class Reticulum:
if not os.path.isdir(Reticulum.identitypath):
os.makedirs(Reticulum.identitypath)
if not os.path.isdir(Reticulum.interfacepath):
os.makedirs(Reticulum.interfacepath)
if os.path.isfile(self.configpath):
try:
self.config = ConfigObj(self.configpath)
@@ -273,7 +305,8 @@ class Reticulum:
time.sleep(1.5)
self.__apply_config()
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
RNS.log(f"Utilising cryptography backend \"{RNS.Cryptography.Provider.backend()}\"", RNS.LOG_DEBUG)
RNS.log(f"Configuration loaded from {self.configpath}", RNS.LOG_VERBOSE)
RNS.Identity.load_known_destinations()
@@ -295,6 +328,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()
@@ -324,11 +358,19 @@ class Reticulum:
interface.bitrate = Reticulum._force_shared_instance_bitrate
interface._force_bitrate = Reticulum._force_shared_instance_bitrate
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
RNS.Transport.interfaces.append(interface)
interface.optimise_mtu()
self.is_shared_instance = True
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
self.__start_jobs()
if self.require_shared == True:
interface.detach()
self.is_shared_instance = True
RNS.log("Existing shared instance required, but this instance started as shared instance. Aborting startup.", RNS.LOG_VERBOSE)
else:
RNS.Transport.interfaces.append(interface)
self.shared_instance_interface = interface
self.is_shared_instance = True
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
self.__start_jobs()
except Exception as e:
try:
@@ -342,6 +384,7 @@ class Reticulum:
interface.bitrate = Reticulum._force_shared_instance_bitrate
interface._force_bitrate = True
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
interface.optimise_mtu()
RNS.Transport.interfaces.append(interface)
self.is_shared_instance = False
self.is_standalone_instance = False
@@ -356,6 +399,10 @@ class Reticulum:
self.is_shared_instance = False
self.is_standalone_instance = True
self.is_connected_to_shared_instance = False
if self.is_shared_instance and self.require_shared:
raise SystemError("No shared instance available, but application that started Reticulum required it")
else:
self.is_shared_instance = False
self.is_standalone_instance = True
@@ -398,6 +445,10 @@ class Reticulum:
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.__transport_enabled = True
if option == "link_mtu_discovery":
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.__link_mtu_discovery = True
if option == "enable_remote_management":
v = self.config["reticulum"].as_bool(option)
if v == True:
@@ -441,14 +492,7 @@ class Reticulum:
if "interfaces" in self.config:
for name in self.config["interfaces"]:
if not name in interface_names:
# TODO: We really need to generalise this way of instantiating
# and configuring interfaces. Ideally, interfaces should just
# have a conrfig dict passed to their init method, and return
# a ready interface, onto which this routine can configure any
# generic or extra parameters.
c = self.config["interfaces"][name]
interface_mode = Interface.Interface.MODE_FULL
if "interface_mode" in c:
@@ -551,493 +595,23 @@ class Reticulum:
announce_cap = c.as_float("announce_cap")/100.0
try:
interface = None
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
if c["type"] == "AutoInterface":
group_id = c["group_id"] if "group_id" in c else None
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
multicast_address_type = c["multicast_address_type"] if "multicast_address_type" in c else None
data_port = int(c["data_port"]) if "data_port" in c else None
allowed_interfaces = c.as_list("devices") if "devices" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
interface = AutoInterface.AutoInterface(
RNS.Transport,
name,
group_id,
discovery_scope,
discovery_port,
multicast_address_type,
data_port,
allowed_interfaces,
ignored_interfaces
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
if c["type"] == "UDPInterface":
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
listen_ip = c["listen_ip"] if "listen_ip" in c else None
listen_port = int(c["listen_port"]) if "listen_port" in c else None
forward_ip = c["forward_ip"] if "forward_ip" in c else None
forward_port = int(c["forward_port"]) if "forward_port" in c else None
if port != None:
if listen_port == None:
listen_port = port
if forward_port == None:
forward_port = port
interface = UDPInterface.UDPInterface(
RNS.Transport,
name,
device,
listen_ip,
listen_port,
forward_ip,
forward_port
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
if c["type"] == "TCPServerInterface":
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
listen_ip = c["listen_ip"] if "listen_ip" in c else None
listen_port = int(c["listen_port"]) if "listen_port" in c else None
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
if port != None:
listen_port = port
interface = TCPInterface.TCPServerInterface(
RNS.Transport,
name,
device,
listen_ip,
listen_port,
i2p_tunneled
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
if c["type"] == "TCPClientInterface":
kiss_framing = False
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
kiss_framing = True
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
tcp_connect_timeout = c.as_int("connect_timeout") if "connect_timeout" in c else None
interface = TCPInterface.TCPClientInterface(
RNS.Transport,
name,
c["target_host"],
int(c["target_port"]),
kiss_framing = kiss_framing,
i2p_tunneled = i2p_tunneled,
connect_timeout = tcp_connect_timeout,
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 16
if c["type"] == "I2PInterface":
i2p_peers = c.as_list("peers") if "peers" in c else None
connectable = c.as_bool("connectable") if "connectable" in c else False
if ifac_size == None:
ifac_size = 16
interface = I2PInterface.I2PInterface(
RNS.Transport,
name,
Reticulum.storagepath,
i2p_peers,
connectable = connectable,
ifac_size = ifac_size,
ifac_netname = ifac_netname,
ifac_netkey = ifac_netkey,
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if c["type"] == "SerialInterface":
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
if port == None:
raise ValueError("No port specified for serial interface")
interface = SerialInterface.SerialInterface(
RNS.Transport,
name,
port,
speed,
databits,
parity,
stopbits
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
if c["type"] == "PipeInterface":
command = c["command"] if "command" in c else None
respawn_delay = c.as_float("respawn_delay") if "respawn_delay" in c else None
if command == None:
raise ValueError("No command specified for PipeInterface")
interface = PipeInterface.PipeInterface(
RNS.Transport,
name,
command,
respawn_delay,
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
if c["type"] == "KISSInterface":
preamble = int(c["preamble"]) if "preamble" in c else None
txtail = int(c["txtail"]) if "txtail" in c else None
persistence = int(c["persistence"]) if "persistence" in c else None
slottime = int(c["slottime"]) if "slottime" in c else None
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
beacon_interval = int(c["id_interval"]) if "id_interval" in c else None
beacon_data = c["id_callsign"] if "id_callsign" in c else None
if port == None:
raise ValueError("No port specified for serial interface")
interface = KISSInterface.KISSInterface(
RNS.Transport,
name,
port,
speed,
databits,
parity,
stopbits,
preamble,
txtail,
persistence,
slottime,
flow_control,
beacon_interval,
beacon_data
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
if c["type"] == "AX25KISSInterface":
preamble = int(c["preamble"]) if "preamble" in c else None
txtail = int(c["txtail"]) if "txtail" in c else None
persistence = int(c["persistence"]) if "persistence" in c else None
slottime = int(c["slottime"]) if "slottime" in c else None
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
port = c["port"] if "port" in c else None
speed = int(c["speed"]) if "speed" in c else 9600
databits = int(c["databits"]) if "databits" in c else 8
parity = c["parity"] if "parity" in c else "N"
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
callsign = c["callsign"] if "callsign" in c else ""
ssid = int(c["ssid"]) if "ssid" in c else -1
if port == None:
raise ValueError("No port specified for serial interface")
interface = AX25KISSInterface.AX25KISSInterface(
RNS.Transport,
name,
callsign,
ssid,
port,
speed,
databits,
parity,
stopbits,
preamble,
txtail,
persistence,
slottime,
flow_control
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
if c["type"] == "RNodeInterface":
frequency = int(c["frequency"]) if "frequency" in c else None
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else None
txpower = int(c["txpower"]) if "txpower" in c else None
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
codingrate = int(c["codingrate"]) if "codingrate" in c else None
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
id_interval = int(c["id_interval"]) if "id_interval" in c else None
id_callsign = c["id_callsign"] if "id_callsign" in c else None
st_alock = float(c["airtime_limit_short"]) if "airtime_limit_short" in c else None
lt_alock = float(c["airtime_limit_long"]) if "airtime_limit_long" in c else None
port = c["port"] if "port" in c else None
if port == None:
raise ValueError("No port specified for RNode interface")
interface = RNodeInterface.RNodeInterface(
RNS.Transport,
name,
port,
frequency = frequency,
bandwidth = bandwidth,
txpower = txpower,
sf = spreadingfactor,
cr = codingrate,
flow_control = flow_control,
id_interval = id_interval,
id_callsign = id_callsign,
st_alock = st_alock,
lt_alock = lt_alock
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
if c["type"] == "RNodeMultiInterface":
count = 0
enabled_count = 0
# Count how many interfaces are in the file
for subinterface in c:
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance(c[subinterface], str):
count += 1
# Count how many interfaces are enabled to allow for appropriate matrix sizing
for subinterface in c:
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance(c[subinterface], str):
subinterface_config = self.config["interfaces"][name][subinterface]
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
enabled_count += 1
# Create an array with a row for each subinterface
subint_config = [[0 for x in range(10)] 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
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
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = 8
def interface_post_init(interface):
if interface != None:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
interface.optimise_mtu()
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = interface.DEFAULT_IFAC_SIZE
interface.announce_rate_target = announce_rate_target
interface.announce_rate_grace = announce_rate_grace
interface.announce_rate_penalty = announce_rate_penalty
@@ -1075,6 +649,103 @@ class Reticulum:
RNS.Transport.interfaces.append(interface)
interface = None
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
interface_config = c
interface_config["name"] = name
interface_config["selected_interface_mode"] = interface_mode
interface_config["configured_bitrate"] = configured_bitrate
if c["type"] == "AutoInterface":
interface = AutoInterface.AutoInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "UDPInterface":
interface = UDPInterface.UDPInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "TCPServerInterface":
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface = TCPInterface.TCPServerInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "TCPClientInterface":
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface = TCPInterface.TCPClientInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "I2PInterface":
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface_config["storagepath"] = Reticulum.storagepath
interface_config["ifac_netname"] = ifac_netname
interface_config["ifac_netkey"] = ifac_netkey
interface_config["ifac_size"] = ifac_size
interface = I2PInterface.I2PInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "SerialInterface":
interface = SerialInterface.SerialInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "PipeInterface":
interface = PipeInterface.PipeInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "KISSInterface":
interface = KISSInterface.KISSInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "AX25KISSInterface":
interface = AX25KISSInterface.AX25KISSInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "RNodeInterface":
interface = RNodeInterface.RNodeInterface(RNS.Transport, interface_config)
interface_post_init(interface)
if c["type"] == "RNodeMultiInterface":
interface = RNodeMultiInterface.RNodeMultiInterface(RNS.Transport, interface_config)
interface_post_init(interface)
interface.start()
if interface == None:
# Interface was not handled by any internal interface types,
# attempt to load and initialise it from user-supplied modules
interface_type = c["type"]
interface_file = f"{interface_type}.py"
interface_path = os.path.join(self.interfacepath, interface_file)
if not os.path.isfile(interface_path):
RNS.log(f"Could not locate external interface module \"{interface_file}\" in \"{self.interfacepath}\"", RNS.LOG_ERROR)
else:
try:
RNS.log(f"Loading external interface \"{interface_file}\" from \"{self.interfacepath}\"", RNS.LOG_NOTICE)
interface_globals = {}
interface_globals["Interface"] = Interface.Interface
interface_globals["RNS"] = RNS
with open(interface_path) as class_file:
interface_code = class_file.read()
exec(interface_code, interface_globals)
interface_class = interface_globals["interface_class"]
if interface_class != None:
interface = interface_class(RNS.Transport, interface_config)
interface_post_init(interface)
except Exception as e:
RNS.log(f"External interface initialisation failed for {interface_type} / {name}", RNS.LOG_ERROR)
RNS.trace_exception(e)
else:
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
@@ -1088,7 +759,7 @@ class Reticulum:
RNS.log("System interfaces are ready", RNS.LOG_VERBOSE)
def _add_interface(self,interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
def _add_interface(self, interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
if not self.is_connected_to_shared_instance:
if interface != None and issubclass(type(interface), RNS.Interfaces.Interface.Interface):
@@ -1098,6 +769,7 @@ class Reticulum:
if configured_bitrate:
interface.bitrate = configured_bitrate
interface.optimise_mtu()
if ifac_size != None:
interface.ifac_size = ifac_size
@@ -1254,6 +926,10 @@ class Reticulum:
else:
ifstats["clients"] = None
if hasattr(interface, "parent_interface") and interface.parent_interface != None:
ifstats["parent_interface_name"] = str(interface.parent_interface)
ifstats["parent_interface_hash"] = interface.parent_interface.get_hash()
if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
if interface.connectable:
ifstats["i2p_connectable"] = True
@@ -1291,12 +967,38 @@ class Reticulum:
if hasattr(interface, "r_channel_load_long"):
ifstats["channel_load_long"] = interface.r_channel_load_long
if hasattr(interface, "r_noise_floor"):
ifstats["noise_floor"] = interface.r_noise_floor
if hasattr(interface, "r_battery_state"):
if interface.r_battery_state != 0x00:
ifstats["battery_state"] = interface.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
else:
ifstats["bitrate"] = None
if hasattr(interface, "current_rx_speed"):
if interface.current_rx_speed != None:
ifstats["rxs"] = interface.current_rx_speed
else:
ifstats["rxs"] = 0
else:
ifstats["rxs"] = 0
if hasattr(interface, "current_tx_speed"):
if interface.current_tx_speed != None:
ifstats["txs"] = interface.current_tx_speed
else:
ifstats["txs"] = 0
else:
ifstats["txs"] = 0
if hasattr(interface, "peers"):
if interface.peers != None:
ifstats["peers"] = len(interface.peers)
@@ -1319,6 +1021,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()
@@ -1331,6 +1036,10 @@ class Reticulum:
stats = {}
stats["interfaces"] = interfaces
stats["rxb"] = RNS.Transport.traffic_rxb
stats["txb"] = RNS.Transport.traffic_txb
stats["rxs"] = RNS.Transport.speed_rx
stats["txs"] = RNS.Transport.speed_tx
if Reticulum.transport_enabled():
stats["transport_id"] = RNS.Transport.identity.hash
stats["transport_uptime"] = time.time()-RNS.Transport.start_time
@@ -1339,6 +1048,13 @@ class Reticulum:
else:
stats["probe_responder"] = None
if importlib.util.find_spec('psutil') != None:
import psutil
process = psutil.Process()
stats["rss"] = process.memory_info().rss
else:
stats["rss"] = None
return stats
def get_path_table(self, max_hops=None):
@@ -1519,6 +1235,14 @@ class Reticulum:
return None
def halt_interface(self, interface):
pass
def resume_interface(self, interface):
pass
def reload_interface(self, interface):
pass
@staticmethod
def should_use_implicit_proof():
@@ -1543,6 +1267,20 @@ class Reticulum:
"""
return Reticulum.__transport_enabled
@staticmethod
def link_mtu_discovery():
"""
Returns whether link MTU discovery is enabled for the running
instance.
When link MTU discovery is enabled, Reticulum will
automatically upgrade link MTUs to the highest supported
value, increasing transfer speed and efficiency.
:returns: True if link MTU discovery is enabled, False if not.
"""
return Reticulum.__link_mtu_discovery
@staticmethod
def remote_management_enabled():
"""
+277 -85
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
@@ -25,6 +25,7 @@ import RNS
import time
import math
import struct
import inspect
import threading
from time import sleep
from .vendor import umsgpack as umsgpack
@@ -52,20 +53,20 @@ class Transport:
Maximum amount of hops that Reticulum will transport a packet.
"""
PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_G = 5 # Retry grace period
PATHFINDER_RW = 0.5 # Random window for announce rebroadcast
PATHFINDER_E = 60*60*24*7 # Path expiration of one week
AP_PATH_TIME = 60*60*24 # Path expiration of one day for Access Point paths
ROAMING_PATH_TIME = 60*60*6 # Path expiration of 6 hours for Roaming paths
PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_G = 5 # Retry grace period
PATHFINDER_RW = 0.5 # Random window for announce rebroadcast
PATHFINDER_E = 60*60*24*7 # Path expiration of one week
AP_PATH_TIME = 60*60*24 # Path expiration of one day for Access Point paths
ROAMING_PATH_TIME = 60*60*6 # Path expiration of 6 hours for Roaming paths
# TODO: Calculate an optimal number for this in
# various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
PATH_REQUEST_GRACE = 0.4 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RG = 0.6 # Extra grace time for roaming-mode interfaces to allow more suitable peers to respond first
PATH_REQUEST_RG = 1.5 # Extra grace time for roaming-mode interfaces to allow more suitable peers to respond first
PATH_REQUEST_MI = 20 # Minimum interval in seconds for automated path requests
STATE_UNKNOWN = 0x00
@@ -84,7 +85,8 @@ class Transport:
destinations = [] # All active destinations
pending_links = [] # Links that are being established
active_links = [] # Links that are active
packet_hashlist = [] # A list of packet hashes for duplicate detection
packet_hashlist = set() # A list of packet hashes for duplicate detection
packet_hashlist_prev = set()
receipts = [] # Receipts of all outgoing packets for proof processing
# TODO: "destination_table" should really be renamed to "path_table"
@@ -102,9 +104,9 @@ class Transport:
path_requests = {} # A table for storing path request timestamps
path_states = {} # A table for keeping track of path states
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
discovery_pr_tags = [] # A table for keeping track of tagged path requests
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
discovery_pr_tags = [] # A table for keeping track of tagged path requests
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
# Transport control destinations are used
# for control purposes like path requests
@@ -140,6 +142,12 @@ class Transport:
interface_last_jobs = 0.0
interface_jobs_interval = 5.0
traffic_rxb = 0
traffic_txb = 0
speed_rx = 0
speed_tx = 0
traffic_captured = None
identity = None
@staticmethod
@@ -164,7 +172,8 @@ class Transport:
if os.path.isfile(packet_hashlist_path):
try:
file = open(packet_hashlist_path, "rb")
Transport.packet_hashlist = umsgpack.unpackb(file.read())
hashlist_data = umsgpack.unpackb(file.read())
Transport.packet_hashlist = set(hashlist_data)
file.close()
except Exception as e:
RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -192,6 +201,9 @@ class Transport:
thread = threading.Thread(target=Transport.jobloop, daemon=True)
thread.start()
thread = threading.Thread(target=Transport.count_traffic_loop, daemon=True)
thread.start()
if RNS.Reticulum.transport_enabled():
destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
@@ -301,12 +313,57 @@ class Transport:
RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE)
Transport.start_time = time.time()
# Sort interfaces according to bitrate
Transport.prioritize_interfaces()
# Synthesize tunnels for any interfaces wanting it
for interface in Transport.interfaces:
interface.tunnel_id = None
if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
Transport.synthesize_tunnel(interface)
@staticmethod
def prioritize_interfaces():
try:
Transport.interfaces.sort(key=lambda interface: interface.bitrate, reverse=True)
except Exception as e:
RNS.log(f"Could not prioritize interfaces according to bitrate. The contained exception was: {e}", RNS.LOG_ERROR)
@staticmethod
def count_traffic_loop():
while True:
time.sleep(1)
try:
rxb = 0; txb = 0;
rxs = 0; txs = 0;
for interface in Transport.interfaces:
if not hasattr(interface, "parent_interface") or interface.parent_interface == None:
if hasattr(interface, "transport_traffic_counter"):
now = time.time(); irxb = interface.rxb; itxb = interface.txb
tc = interface.transport_traffic_counter
rx_diff = irxb - tc["rxb"]
tx_diff = itxb - tc["txb"]
ts_diff = now - tc["ts"]
rxb += rx_diff; crxs = (rx_diff*8)/ts_diff
txb += tx_diff; ctxs = (tx_diff*8)/ts_diff
interface.current_rx_speed = crxs; rxs += crxs
interface.current_tx_speed = ctxs; txs += ctxs
tc["rxb"] = irxb;
tc["txb"] = itxb;
tc["ts"] = now;
else:
interface.transport_traffic_counter = {"ts": time.time(), "rxb": interface.rxb, "txb": interface.txb}
Transport.traffic_rxb += rxb
Transport.traffic_txb += txb
Transport.speed_rx = rxs
Transport.speed_tx = txs
except Exception as e:
RNS.log(f"An error occurred while counting interface traffic: {e}", RNS.LOG_ERROR)
@staticmethod
def jobloop():
while (True):
@@ -403,7 +460,8 @@ class Transport:
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface
attached_interface = attached_interface,
context_flag = packet.context_flag,
)
new_packet.hops = announce_entry[4]
@@ -433,8 +491,9 @@ class Transport:
# Cull the packet hashlist if it has reached its max size
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize:
Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1]
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize//2:
Transport.packet_hashlist_prev = Transport.packet_hashlist
Transport.packet_hashlist = set()
# Cull the path request tags list if it has reached its max size
if len(Transport.discovery_pr_tags) > Transport.max_pr_tags:
@@ -663,6 +722,7 @@ class Transport:
Transport.tables_last_culled = time.time()
if time.time() > Transport.interface_last_jobs + Transport.interface_jobs_interval:
Transport.prioritize_interfaces()
for interface in Transport.interfaces:
interface.process_held_announces()
Transport.interface_last_jobs = time.time()
@@ -730,10 +790,10 @@ class Transport:
i += 1
# Send it
interface.processOutgoing(masked_raw)
interface.process_outgoing(masked_raw)
else:
interface.processOutgoing(raw)
interface.process_outgoing(raw)
except Exception as e:
RNS.log("Error while transmitting on "+str(interface)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -969,10 +1029,10 @@ class Transport:
else:
pass
if should_transmit:
if not stored_hash:
Transport.packet_hashlist.append(packet.packet_hash)
Transport.packet_hashlist.add(packet.packet_hash)
stored_hash = True
# TODO: Re-evaluate potential for blocking
@@ -1038,7 +1098,7 @@ class Transport:
RNS.log("Dropped invalid GROUP announce packet", RNS.LOG_DEBUG)
return False
if not packet.packet_hash in Transport.packet_hashlist:
if not packet.packet_hash in Transport.packet_hashlist and not packet.packet_hash in Transport.packet_hashlist_prev:
return True
else:
if packet.packet_type == RNS.Packet.ANNOUNCE:
@@ -1136,29 +1196,23 @@ class Transport:
if hasattr(interface, "r_stat_rssi"):
if interface.r_stat_rssi != None:
packet.rssi = interface.r_stat_rssi
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_rssi_cache.pop(0)
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_rssi_cache.pop(0)
if hasattr(interface, "r_stat_snr"):
if interface.r_stat_rssi != None:
packet.snr = interface.r_stat_snr
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_snr_cache.pop(0)
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_snr_cache.pop(0)
if hasattr(interface, "r_stat_q"):
if interface.r_stat_q != None:
packet.q = interface.r_stat_q
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_q_cache.append([packet.packet_hash, packet.q])
while len(Transport.local_client_q_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_q_cache.pop(0)
Transport.local_client_q_cache.append([packet.packet_hash, packet.q])
while len(Transport.local_client_q_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_q_cache.pop(0)
if len(Transport.local_client_interfaces) > 0:
if Transport.is_local_client_interface(interface):
@@ -1189,7 +1243,7 @@ class Transport:
remember_packet_hash = False
if remember_packet_hash:
Transport.packet_hashlist.append(packet.packet_hash)
Transport.packet_hashlist.add(packet.packet_hash)
# TODO: Enable when caching has been redesigned
# Transport.cache(packet)
@@ -1274,6 +1328,24 @@ class Transport:
now = time.time()
proof_timeout = Transport.extra_link_proof_timeout(packet.receiving_interface)
proof_timeout += now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops)
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
nh_mtu = outbound_interface.HW_MTU
if path_mtu:
if outbound_interface.HW_MTU == None:
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
path_mtu = None
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
elif not outbound_interface.AUTOCONFIGURE_MTU:
RNS.log(f"Outbound interface doesn't support MTU autoconfiguration, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
path_mtu = None
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
else:
if nh_mtu < path_mtu:
path_mtu = nh_mtu
clamped_mtu = RNS.Link.mtu_bytes(path_mtu)
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
# Entry format is
link_entry = [ now, # 0: Timestamp,
@@ -1286,7 +1358,7 @@ class Transport:
False, # 7: Validated
proof_timeout] # 8: Proof timeout timestamp
Transport.link_table[packet.getTruncatedHash()] = link_entry
Transport.link_table[RNS.Link.link_id_from_lr_packet(packet)] = link_entry
else:
# Entry format is
@@ -1336,15 +1408,17 @@ class Transport:
# Add this packet to the filter hashlist if we
# have determined that it's actually our turn
# to process it.
Transport.packet_hashlist.append(packet.packet_hash)
Transport.packet_hashlist.add(packet.packet_hash)
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[2:]
Transport.transmit(outbound_interface, new_raw)
Transport.link_table[packet.destination_hash][0] = time.time()
else:
pass
# TODO: Test and possibly enable this at some point
# Transport.jobs_locked = False
# return
# Announce handling. Handles logic related to incoming
@@ -1387,7 +1461,8 @@ class Transport:
now = time.time()
if now < announce_entry[1]:
RNS.log("Rebroadcasted announce for "+RNS.prettyhexrep(packet.destination_hash)+" has been passed on to another node, no further tries needed", RNS.LOG_DEBUG)
Transport.announce_table.pop(packet.destination_hash)
if packet.destination_hash in Transport.announce_table:
Transport.announce_table.pop(packet.destination_hash)
else:
received_from = packet.destination_hash
@@ -1415,7 +1490,8 @@ class Transport:
# replayed to forge paths.
# TODO: Check whether this approach works
# under all circumstances
if not random_blob in random_blobs:
path_timebase = Transport.timebase_from_random_blobs(random_blobs)
if not random_blob in random_blobs and announce_emitted > path_timebase:
Transport.mark_path_unknown_state(packet.destination_hash)
should_add = True
else:
@@ -1601,7 +1677,8 @@ class Transport:
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = local_interface
attached_interface = local_interface,
context_flag = packet.context_flag,
)
new_announce.hops = packet.hops
@@ -1618,7 +1695,8 @@ class Transport:
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = local_interface
attached_interface = local_interface,
context_flag = packet.context_flag,
)
new_announce.hops = packet.hops
@@ -1649,7 +1727,8 @@ class Transport:
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface
attached_interface = attached_interface,
context_flag = packet.context_flag,
)
new_announce.hops = packet.hops
@@ -1671,36 +1750,72 @@ class Transport:
# Call externally registered callbacks from apps
# wanting to know when an announce arrives
if packet.context != RNS.Packet.PATH_RESPONSE:
for handler in Transport.announce_handlers:
try:
# Check that the announced destination matches
# the handlers aspect filter
execute_callback = False
announce_identity = RNS.Identity.recall(packet.destination_hash)
if handler.aspect_filter == None:
# If the handlers aspect filter is set to
# None, we execute the callback in all cases
for handler in Transport.announce_handlers:
try:
# Check that the announced destination matches
# the handlers aspect filter
execute_callback = False
announce_identity = RNS.Identity.recall(packet.destination_hash)
if handler.aspect_filter == None:
# If the handlers aspect filter is set to
# None, we execute the callback in all cases
execute_callback = True
else:
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
if packet.destination_hash == handler_expected_hash:
execute_callback = True
# If this is a path response, check whether the
# handler wants to receive it.
if packet.context == RNS.Packet.PATH_RESPONSE:
if hasattr(handler, "receive_path_responses") and handler.receive_path_responses == True:
pass
else:
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
if packet.destination_hash == handler_expected_hash:
execute_callback = True
if execute_callback:
handler.received_announce(
destination_hash=packet.destination_hash,
announced_identity=announce_identity,
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
)
except Exception as e:
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
execute_callback = False
if execute_callback:
if len(inspect.signature(handler.received_announce).parameters) == 3:
handler.received_announce(destination_hash=packet.destination_hash,
announced_identity=announce_identity,
app_data=RNS.Identity.recall_app_data(packet.destination_hash))
elif len(inspect.signature(handler.received_announce).parameters) == 4:
handler.received_announce(destination_hash=packet.destination_hash,
announced_identity=announce_identity,
app_data=RNS.Identity.recall_app_data(packet.destination_hash),
announce_packet_hash = packet.packet_hash)
else:
raise TypeError("Invalid signature for announce handler callback")
except Exception as e:
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
# Handling for link requests to local destinations
elif packet.packet_type == RNS.Packet.LINKREQUEST:
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
for destination in Transport.destinations:
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
if packet.receiving_interface.AUTOCONFIGURE_MTU:
nh_mtu = packet.receiving_interface.HW_MTU
else:
nh_mtu = RNS.Reticulum.MTU
if path_mtu:
if packet.receiving_interface.HW_MTU == None:
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
path_mtu = None
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]
else:
if nh_mtu < path_mtu:
path_mtu = nh_mtu
clamped_mtu = RNS.Link.mtu_bytes(path_mtu)
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
packet.destination = destination
destination.receive(packet)
@@ -1709,8 +1824,28 @@ class Transport:
if packet.destination_type == RNS.Destination.LINK:
for link in Transport.active_links:
if link.link_id == packet.destination_hash:
packet.link = link
link.receive(packet)
if link.attached_interface == packet.receiving_interface:
packet.link = link
if packet.context == RNS.Packet.CACHE_REQUEST:
cached_packet = Transport.get_cached_packet(packet.data)
if cached_packet != None:
cached_packet.unpack()
RNS.Packet(destination=link, data=cached_packet.data,
packet_type=cached_packet.packet_type, context=cached_packet.context).send()
Transport.jobs_locked = False
else:
link.receive(packet)
else:
# In the strange and rare case that an interface
# is partly malfunctioning, and a link-associated
# packet is being received on an interface that
# has failed sending, and transport has failed over
# to another path, we remove this packet hash from
# the filter hashlist so the link can receive the
# packet when it finally arrives over another path.
while packet.packet_hash in Transport.packet_hashlist:
Transport.packet_hashlist.remove(packet.packet_hash)
else:
for destination in Transport.destinations:
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
@@ -1738,12 +1873,16 @@ class Transport:
if packet.hops == link_entry[3]:
if packet.receiving_interface == link_entry[2]:
try:
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2:
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2 or len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE:
mtu_bytes = b""
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE:
mtu_bytes = RNS.Link.mtu_bytes(RNS.Link.mtu_from_lp_packet(packet))
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2]
peer_identity = RNS.Identity.recall(link_entry[6])
peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE]
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes+mtu_bytes
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
if peer_identity.validate(signature, signed_data):
@@ -1785,7 +1924,7 @@ class Transport:
# Add this packet to the filter hashlist if we
# have determined that it's actually destined
# for this system, and then validate the proof
Transport.packet_hashlist.append(packet.packet_hash)
Transport.packet_hashlist.add(packet.packet_hash)
link.validate_proof(packet)
elif packet.context == RNS.Packet.RESOURCE_PRF:
@@ -1973,7 +2112,10 @@ class Transport:
"""
Registers an announce handler.
:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example<example-announce>` for more info.
:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)*
or *received_announce(destination_hash, announced_identity, app_data, announce_packet_hash)* callable. Can optionally have a
*receive_path_responses* attribute set to ``True``, to also receive all path responses, in addition to live announces. See
the :ref:`Announce Example<example-announce>` for more info.
"""
if hasattr(handler, "received_announce") and callable(handler.received_announce):
if hasattr(handler, "aspect_filter"):
@@ -2134,6 +2276,17 @@ class Transport:
else:
return None
@staticmethod
def next_hop_interface_hw_mtu(destination_hash):
next_hop_interface = Transport.next_hop_interface(destination_hash)
if next_hop_interface != None:
if next_hop_interface.AUTOCONFIGURE_MTU:
return next_hop_interface.HW_MTU
else:
return None
else:
return None
@staticmethod
def next_hop_per_bit_latency(destination_hash):
next_hop_interface_bitrate = Transport.next_hop_interface_bitrate(destination_hash)
@@ -2510,7 +2663,7 @@ class Transport:
# Currently no rules are being applied
# here, and all interfaces will be sent
# the detach call on RNS teardown.
if True:
if not interface.detached:
detachable_interfaces.append(interface)
else:
pass
@@ -2519,17 +2672,45 @@ class Transport:
# Currently no rules are being applied
# here, and all interfaces will be sent
# the detach call on RNS teardown.
if True:
if not interface.detached:
detachable_interfaces.append(interface)
else:
pass
shared_instance_master = None
local_interfaces = []
detach_threads = []
RNS.log("Detaching interfaces", RNS.LOG_DEBUG)
for interface in detachable_interfaces:
try:
interface.detach()
if type(interface) == RNS.Interfaces.LocalInterface.LocalServerInterface:
shared_instance_master = interface
elif type(interface) == RNS.Interfaces.LocalInterface.LocalClientInterface:
local_interfaces.append(interface)
else:
def detach_job():
RNS.log(f"Detaching {interface}", RNS.LOG_EXTREME)
interface.detach()
dt = threading.Thread(target=detach_job, daemon=False)
dt.start()
detach_threads.append(dt)
except Exception as e:
RNS.log("An error occurred while detaching "+str(interface)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
for dt in detach_threads:
dt.join()
RNS.log("Detaching local clients", RNS.LOG_DEBUG)
for li in local_interfaces:
li.detach()
RNS.log("Detaching shared instance", RNS.LOG_DEBUG)
if shared_instance_master != None:
shared_instance_master.detach()
RNS.log("All interfaces detached", RNS.LOG_DEBUG)
@staticmethod
def shared_connection_disappeared():
for link in Transport.active_links:
@@ -2569,15 +2750,26 @@ class Transport:
interface.announce_queue = []
RNS.log("Dropped "+na_str+" on "+str(interface), RNS.LOG_VERBOSE)
@staticmethod
def timebase_from_random_blob(random_blob):
return int.from_bytes(random_blob[5:10], "big")
@staticmethod
def timebase_from_random_blobs(random_blobs):
timebase = 0
for random_blob in random_blobs:
emitted = Transport.timebase_from_random_blob(random_blob)
if emitted > timebase: timebase = emitted
return timebase
@staticmethod
def announce_emitted(packet):
random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
announce_emitted = int.from_bytes(random_blob[5:10], "big")
announce_emitted = Transport.timebase_from_random_blob(random_blob)
return announce_emitted
@staticmethod
def save_packet_hashlist():
if not Transport.owner.is_connected_to_shared_instance:
@@ -2596,13 +2788,13 @@ class Transport:
save_start = time.time()
if not RNS.Reticulum.transport_enabled():
Transport.packet_hashlist = []
Transport.packet_hashlist = set()
else:
RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
file = open(packet_hashlist_path, "wb")
file.write(umsgpack.packb(Transport.packet_hashlist))
file.write(umsgpack.packb(list(Transport.packet_hashlist.copy())))
file.close()
save_time = time.time() - save_start
@@ -2637,7 +2829,7 @@ class Transport:
RNS.log("Saving path table to storage...", RNS.LOG_DEBUG)
serialised_destinations = []
for destination_hash in Transport.destination_table:
for destination_hash in Transport.destination_table.copy():
# Get the destination entry from the destination table
de = Transport.destination_table[destination_hash]
interface_hash = de[5].get_hash()
@@ -2707,10 +2899,10 @@ class Transport:
RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG)
serialised_tunnels = []
for tunnel_id in Transport.tunnels:
for tunnel_id in Transport.tunnels.copy():
te = Transport.tunnels[tunnel_id]
interface = te[1]
tunnel_paths = te[2]
tunnel_paths = te[2].copy()
expires = te[3]
if interface != None:
+4 -2
View File
@@ -23,5 +23,7 @@
import os
import glob
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
+161 -56
View File
@@ -35,13 +35,18 @@ APP_NAME = "rncp"
allow_all = False
allow_fetch = False
fetch_jail = None
save_path = None
show_phy_rates = False
allowed_identity_hashes = []
REQ_FETCH_NOT_ALLOWED = 0xF0
es = " "
erase_str = "\33[2K\r"
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False,
limit = None, disable_auth = None, fetch_allowed = False, jail = None, announce = False):
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail
limit = None, disable_auth = None, fetch_allowed = False, jail = None, save = None, announce = False):
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path
from tempfile import TemporaryFile
allow_fetch = fetch_allowed
@@ -56,6 +61,20 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
fetch_jail = os.path.abspath(os.path.expanduser(jail))
RNS.log("Restricting fetch requests to paths under \""+fetch_jail+"\"", RNS.LOG_VERBOSE)
if save != None:
sp = os.path.abspath(os.path.expanduser(save))
if os.path.isdir(sp):
if os.access(sp, os.W_OK):
save_path = sp
else:
RNS.log("Output directory not writable", RNS.LOG_ERROR)
exit(4)
else:
RNS.log("Output directory not found", RNS.LOG_ERROR)
exit(3)
RNS.log("Saving received files in \""+save_path+"\"", RNS.LOG_VERBOSE)
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
@@ -130,10 +149,15 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
if not allow_fetch:
return REQ_FETCH_NOT_ALLOWED
file_path = os.path.abspath(os.path.expanduser(data))
if fetch_jail:
if not file_path.startswith(jail):
if data.startswith(fetch_jail+"/"):
data = data.replace(fetch_jail+"/", "")
file_path = os.path.abspath(os.path.expanduser(f"{fetch_jail}/{data}"))
if not file_path.startswith(fetch_jail+"/"):
RNS.log(f"Disallowing fetch request for {file_path} outside of fetch jail {fetch_jail}", RNS.LOG_WARNING)
return REQ_FETCH_NOT_ALLOWED
else:
file_path = os.path.abspath(os.path.expanduser(f"{data}"))
target_link = None
for link in RNS.Transport.active_links:
@@ -168,7 +192,13 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
destination.set_link_established_callback(client_link_established)
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
if allow_fetch:
if allow_all:
RNS.log("Allowing unauthenticated fetch requests", RNS.LOG_WARNING)
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_ALL)
else:
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
if announce >= 0:
@@ -227,6 +257,7 @@ def receive_resource_started(resource):
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
def receive_resource_concluded(resource):
global save_path
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
@@ -235,12 +266,20 @@ def receive_resource_concluded(resource):
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
if not saved_filename.startswith(save_path+"/"):
RNS.log(f"Invalid save path {saved_filename}, ignoring", RNS.LOG_ERROR)
return
else:
saved_filename = filename
full_save_path = saved_filename
while os.path.isfile(full_save_path):
counter += 1
saved_filename = filename+"."+str(counter)
full_save_path = saved_filename+"."+str(counter)
file = open(saved_filename, "wb")
file = open(full_save_path, "wb")
file.write(resource.data.read())
file.close()
@@ -254,33 +293,59 @@ resource_done = False
current_resource = None
stats = []
speed = 0.0
phy_speed = 0.0
phy_got_total = 0
def sender_progress(resource):
stats_max = 32
global current_resource, stats, speed, resource_done
global current_resource, stats, speed, phy_speed, phy_got_total, resource_done
current_resource = resource
now = time.time()
got = current_resource.get_progress()*current_resource.total_size
entry = [now, got]
got = current_resource.get_progress()*current_resource.get_data_size()
phy_got = current_resource.get_segment_progress()*current_resource.get_transfer_size()
entry = [now, got, phy_got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
phy_speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
phy_diff = phy_got - stats[0][2]
if phy_diff > 0:
phy_speed = phy_diff/span
# phy_got_total += phy_diff
if resource.status < RNS.Resource.COMPLETE:
resource_done = False
else:
resource_done = True
link = None
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
global current_resource, resource_done, link, speed
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None):
global current_resource, resource_done, link, speed, show_phy_rates, save_path
targetloglevel = 3+verbosity-quietness
show_phy_rates = phy_rates
if save:
sp = os.path.abspath(os.path.expanduser(save))
if os.path.isdir(sp):
if os.access(sp, os.W_OK):
save_path = sp
else:
RNS.log("Output directory not writable", RNS.LOG_ERROR)
exit(4)
else:
RNS.log("Output directory not found", RNS.LOG_ERROR)
exit(3)
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
@@ -315,7 +380,7 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
else:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=es)
sys.stdout.flush()
i = 0
@@ -332,13 +397,13 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("Path not found")
else:
print("\r \rPath not found")
print(f"{erase_str}Path not found")
exit(1)
else:
if silent:
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
print(f"{erase_str}Establishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=es)
listener_identity = RNS.Identity.recall(destination_hash)
listener_destination = RNS.Destination(
@@ -361,13 +426,13 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
print(f"{erase_str}Could not establish link with "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
if silent:
print("Requesting file from remote...")
else:
print("\r \rRequesting file from remote ", end=" ")
print(f"{erase_str}Requesting file from remote ", end=es)
link.identify(identity)
@@ -402,18 +467,25 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
def fetch_resource_concluded(resource):
nonlocal resource_resolved, resource_status
global save_path
if resource.status == RNS.Resource.COMPLETE:
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
else:
saved_filename = filename
full_save_path = saved_filename
while os.path.isfile(full_save_path):
counter += 1
saved_filename = filename+"."+str(counter)
file = open(saved_filename, "wb")
full_save_path = saved_filename+"."+str(counter)
file = open(full_save_path, "wb")
file.write(resource.data.read())
file.close()
resource_status = "completed"
@@ -442,31 +514,31 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
i = (i+1)%len(syms)
if request_status == "fetch_not_allowed":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed, fetching the file "+str(file)+" was not allowed by the remote")
link.teardown()
time.sleep(0.15)
exit(0)
elif request_status == "not_found":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed, the file "+str(file)+" was not found on the remote")
link.teardown()
time.sleep(0.15)
exit(0)
elif request_status == "remote_error":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed due to an error on the remote system")
link.teardown()
time.sleep(0.15)
exit(0)
elif request_status == "unknown":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
print("Fetch request failed due to an unknown error (probably not authorised)")
link.teardown()
time.sleep(0.15)
exit(0)
elif request_status == "found":
if not silent: print("\r \r", end="")
if not silent: print(f"{erase_str}", end="")
while not resource_resolved:
if not silent:
@@ -474,13 +546,21 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if current_resource:
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
if prg != 1.0:
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
if show_phy_rates:
pss = size_str(phy_speed, "b")
phy_str = f" ({pss}ps at physical layer)"
else:
print("\r \rTransfer complete "+stat_str, end=" ")
phy_str = ""
ps = size_str(int(prg*current_resource.total_size))
ts = size_str(current_resource.total_size)
ss = size_str(speed, "b")
stat_str = f"{percent}% - {ps} of {ts} - {ss}ps{phy_str}"
if prg != 1.0:
print(f"{erase_str}Transferring file {syms[i]} {stat_str}", end=es)
else:
print(f"{erase_str}Transfer complete {stat_str}", end=es)
else:
print("\r \rWaiting for transfer to start "+syms[i]+" ", end=" ")
print(f"{erase_str}Waiting for transfer to start {syms[i]} ", end=es)
sys.stdout.flush()
i = (i+1)%len(syms)
@@ -488,13 +568,12 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
if silent:
print("The transfer failed")
else:
print("\r \rThe transfer failed")
print(f"{erase_str}The transfer failed")
exit(1)
else:
if silent:
print(str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
else:
#print("\r \r"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
print("\n"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.15)
@@ -504,10 +583,11 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
exit(0)
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
global current_resource, resource_done, link, speed
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, no_compress=False):
global current_resource, resource_done, link, speed, show_phy_rates, phy_got_total, phy_speed
from tempfile import TemporaryFile
targetloglevel = 3+verbosity-quietness
show_phy_rates = phy_rates
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
@@ -536,14 +616,14 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
print("Filename exceeds max size, cannot send")
exit(1)
else:
print("Preparing file...", end=" ")
print("Preparing file...", end=es)
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
print("\r \r", end="")
print(f"{erase_str}", end="")
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
@@ -566,7 +646,7 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
else:
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=es)
sys.stdout.flush()
i = 0
@@ -583,13 +663,13 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("Path not found")
else:
print("\r \rPath not found")
print(f"{erase_str}Path not found")
exit(1)
else:
if silent:
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
print(f"{erase_str}Establishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=es)
receiver_identity = RNS.Identity.recall(destination_hash)
receiver_destination = RNS.Destination(
@@ -612,22 +692,25 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
else:
print("\r \rLink establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
print(f"{erase_str}Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
exit(1)
elif not RNS.Transport.has_path(destination_hash):
if silent:
print("No path found to "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rNo path found to "+RNS.prettyhexrep(destination_hash))
print(f"{erase_str}No path found to "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
if silent:
print("Advertising file resource...")
else:
print("\r \rAdvertising file resource ", end=" ")
print(f"{erase_str}Advertising file resource ", end=es)
link.identify(identity)
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
auto_compress = True
if no_compress:
auto_compress = False
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress, auto_compress = auto_compress)
current_resource = resource
while resource.status < RNS.Resource.TRANSFERRING:
@@ -637,28 +720,38 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
sys.stdout.flush()
i = (i+1)%len(syms)
resource_started_at = time.time()
if resource.status > RNS.Resource.COMPLETE:
if silent:
print("File was not accepted by "+RNS.prettyhexrep(destination_hash))
else:
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
print(f"{erase_str}File was not accepted by "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
if silent:
print("Transferring file...")
else:
print("\r \rTransferring file ", end=" ")
print(f"{erase_str}Transferring file ", end=es)
def progress_update(i, done=False):
time.sleep(0.1)
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
if not done:
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
if show_phy_rates and not resource_done:
pss = size_str(phy_speed, "b")
phy_str = f" ({pss}ps at physical layer)"
else:
print("\r \rTransfer complete "+stat_str, end=" ")
phy_str = ""
es = " "
cs = size_str(int(prg*current_resource.total_size))
ts = size_str(current_resource.total_size)
ss = size_str(speed, "b")
stat_str = f"{percent}% - {cs} of {ts} - {ss}ps{phy_str}"
if not done:
print(f"{erase_str}Transferring file "+syms[i]+" "+stat_str, end=es)
else:
print(f"{erase_str}Transfer complete "+stat_str, end=es)
sys.stdout.flush()
i = (i+1)%len(syms)
return i
@@ -667,6 +760,11 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if not silent:
i = progress_update(i)
resource_concluded_at = time.time()
transfer_time = resource_concluded_at - resource_started_at
speed = current_resource.total_size/transfer_time
# phy_speed = phy_got_total/transfer_time
if not silent:
i = progress_update(i, done=True)
@@ -674,13 +772,12 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
if silent:
print("The transfer failed")
else:
print("\r \rThe transfer failed")
print(f"{erase_str}The transfer failed")
exit(1)
else:
if silent:
print(str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
else:
# print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
print("\n"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.25)
@@ -698,14 +795,17 @@ def main():
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
parser.add_argument("-S", '--silent', action='store_true', default=False, help="disable transfer progress output")
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming transfer requests")
parser.add_argument("-C", '--no-compress', action='store_true', default=False, help="disable automatic compression")
parser.add_argument("-F", '--allow-fetch', action='store_true', default=False, help="allow authenticated clients to fetch files")
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
parser.add_argument("-j", "--jail", metavar="path", action="store", default=None, help="restrict fetch requests to specified path", type=str)
parser.add_argument("-s", "--save", metavar="path", action="store", default=None, help="save received files in specified path", type=str)
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity", type=str)
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity (or add in ~/.rncp/allowed_identities)", type=str)
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept requests from anyone")
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="sender timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
parser.add_argument('-P', '--phy-rates', action='store_true', default=False, help="display physical layer transfer rates")
# parser.add_argument("--limit", action="store", metavar="files", type=float, help="maximum number of files to accept", default=None)
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
@@ -719,6 +819,7 @@ def main():
allowed = args.allowed,
fetch_allowed = args.allow_fetch,
jail = args.jail,
save = args.save,
display_identity=args.print_identity,
# limit=args.limit,
disable_auth=args.no_auth,
@@ -735,6 +836,8 @@ def main():
file = args.file,
timeout = args.w,
silent = args.silent,
phy_rates = args.phy_rates,
save = args.save,
)
else:
print("")
@@ -750,6 +853,8 @@ def main():
file = args.file,
timeout = args.w,
silent = args.silent,
phy_rates = args.phy_rates,
no_compress = args.no_compress,
)
else:
+533 -86
View File
@@ -2,7 +2,7 @@
# MIT License
#
# Copyright (c) 2018-2022 Mark Qvist - unsigned.io/rnode
# Copyright (c) 2018-2025 Mark Qvist
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -41,7 +41,7 @@ import RNS
RNS.logtimefmt = "%H:%M:%S"
RNS.compact_log_fmt = True
program_version = "2.1.3"
program_version = "2.4.0"
eth_addr = "0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73"
btc_addr = "35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH"
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
@@ -81,9 +81,14 @@ class KISS():
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_DISP_INT = 0x45
CMD_NP_INT = 0x65
CMD_DISP_ADR = 0x63
CMD_DISP_BLNK = 0x64
CMD_DISP_ROT = 0x67
CMD_DISP_RCND = 0x68
CMD_BT_CTRL = 0x46
CMD_BT_PIN = 0x62
CMD_DIS_IA = 0x69
CMD_BOARD = 0x47
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
@@ -119,57 +124,80 @@ class KISS():
return data
class ROM():
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
PLATFORM_NRF52 = 0x70
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
PLATFORM_NRF52 = 0x70
MCU_1284P = 0x91
MCU_2560 = 0x92
MCU_ESP32 = 0x81
MCU_NRF52 = 0x71
MCU_1284P = 0x91
MCU_2560 = 0x92
MCU_ESP32 = 0x81
MCU_NRF52 = 0x71
PRODUCT_RAK4631 = 0x10
MODEL_11 = 0x11
MODEL_12 = 0x12
PRODUCT_RNODE = 0x03
MODEL_A1 = 0xA1
MODEL_A6 = 0xA6
MODEL_A4 = 0xA4
MODEL_A9 = 0xA9
MODEL_A3 = 0xA3
MODEL_A8 = 0xA8
MODEL_A2 = 0xA2
MODEL_A7 = 0xA7
MODEL_A5 = 0xA5
MODEL_AA = 0xAA
MODEL_AC = 0xAC
PRODUCT_RNODE = 0x03
MODEL_A1 = 0xA1
MODEL_A6 = 0xA6
MODEL_A4 = 0xA4
MODEL_A9 = 0xA9
MODEL_A3 = 0xA3
MODEL_A8 = 0xA8
MODEL_A2 = 0xA2
MODEL_A7 = 0xA7
PRODUCT_T32_10 = 0xB2
MODEL_BA = 0xBA
MODEL_BB = 0xBB
PRODUCT_T32_10 = 0xB2
MODEL_BA = 0xBA
MODEL_BB = 0xBB
PRODUCT_T32_20 = 0xB0
MODEL_B3 = 0xB3
MODEL_B8 = 0xB8
PRODUCT_T32_20 = 0xB0
MODEL_B3 = 0xB3
MODEL_B8 = 0xB8
PRODUCT_T32_21 = 0xB1
MODEL_B4 = 0xB4
MODEL_B9 = 0xB9
MODEL_B4_TCXO = 0x04 # The TCXO model codes are only used here to select the correct firmware,
MODEL_B9_TCXO = 0x09 # actual model codes in firmware is still 0xB4 and 0xB9.
PRODUCT_H32_V2 = 0xC0
MODEL_C4 = 0xC4
MODEL_C9 = 0xC9
PRODUCT_T32_21 = 0xB1
MODEL_B4 = 0xB4
MODEL_B9 = 0xB9
MODEL_B4_TCXO = 0x04 # The TCXO model codes are only used here to select the
MODEL_B9_TCXO = 0x09 # correct firmware, actual model codes in firmware is
# still 0xB4 and 0xB9.
PRODUCT_H32_V3 = 0xC1
MODEL_C5 = 0xC5
MODEL_CA = 0xCA
PRODUCT_H32_V2 = 0xC0
MODEL_C4 = 0xC4
MODEL_C9 = 0xC9
PRODUCT_TBEAM = 0xE0
MODEL_E4 = 0xE4
MODEL_E9 = 0xE9
MODEL_E3 = 0xE3
MODEL_E8 = 0xE8
PRODUCT_H32_V3 = 0xC1
MODEL_C5 = 0xC5
MODEL_CA = 0xCA
PRODUCT_TBEAM_S_V1 = 0xEA
MODEL_DB = 0xDB
MODEL_DC = 0xDC
PRODUCT_TBEAM = 0xE0
MODEL_E4 = 0xE4
MODEL_E9 = 0xE9
MODEL_E3 = 0xE3
MODEL_E8 = 0xE8
PRODUCT_TDECK = 0xD0
MODEL_D4 = 0xD4
MODEL_D9 = 0xD9
PRODUCT_RAK4631 = 0x10
MODEL_11 = 0x11
MODEL_12 = 0x12
MODEL_13 = 0x13
MODEL_14 = 0x14
PRODUCT_OPENCOM_XL = 0x20
MODEL_21 = 0x21
PRODUCT_TECHO = 0x15
MODEL_16 = 0x16
MODEL_17 = 0x17
PRODUCT_HELTEC_T114 = 0xC2
BOARD_HELTEC_T114 = 0x3C
MODEL_C6 = 0xC6 # Heltec Mesh Node T114, 470-510 MHz (HT-n5262-LF)
MODEL_C7 = 0xC7 # Heltec Mesh Node T114, 863-928 MHz (HT-n5262-HF)
PRODUCT_HMBRW = 0xF0
MODEL_FF = 0xFF
@@ -190,31 +218,49 @@ class ROM():
ADDR_CONF_FREQ = 0xA3
ADDR_CONF_OK = 0xA7
ADDR_CONF_BT = 0xB0
ADDR_CONF_DSET = 0xB1
ADDR_CONF_DINT = 0xB2
ADDR_CONF_DADR = 0xB3
ADDR_CONF_DBLK = 0xB4
ADDR_CONF_DROT = 0xB8
ADDR_CONF_PSET = 0xB5
ADDR_CONF_PINT = 0xB6
ADDR_CONF_BSET = 0xB7
ADDR_CONF_DIA = 0xB9
INFO_LOCK_BYTE = 0x73
CONF_OK_BYTE = 0x73
BOARD_RNODE = 0x31
BOARD_HMBRW = 0x32
BOARD_TBEAM = 0x33
BOARD_TDECK = 0x3B
BOARD_HUZZAH32 = 0x34
BOARD_GENERIC_ESP32 = 0x35
BOARD_LORA32_V2_0 = 0x36
BOARD_LORA32_V2_1 = 0x37
BOARD_TECHO = 0x43
BOARD_RAK4631 = 0x51
MANUAL_FLASH_MODELS = [MODEL_A1, MODEL_A6]
MANUAL_FLASH_MODELS = []
mapped_product = ROM.PRODUCT_RNODE
products = {
ROM.PRODUCT_RNODE: "RNode",
ROM.PRODUCT_HMBRW: "Hombrew RNode",
ROM.PRODUCT_TBEAM: "LilyGO T-Beam",
ROM.PRODUCT_TBEAM_S_V1:"LilyGO T-Beam Supreme",
ROM.PRODUCT_TDECK: "LilyGO T-Deck",
ROM.PRODUCT_T32_10: "LilyGO LoRa32 v1.0",
ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0",
ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1",
ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2",
ROM.PRODUCT_H32_V3: "Heltec LoRa32 v3",
ROM.PRODUCT_TECHO: "LilyGO T-Echo",
ROM.PRODUCT_RAK4631: "RAK4631",
ROM.PRODUCT_OPENCOM_XL: "openCom XL",
ROM.PRODUCT_HELTEC_T114: "Heltec Mesh Node T114",
}
platforms = {
@@ -231,12 +277,13 @@ mcus = {
}
models = {
0x11: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
0x12: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
0xA4: [410000000, 525000000, 14, "410 - 525 MHz", "rnode_firmware.hex", "SX1278"],
0xA9: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware.hex", "SX1276"],
0xA1: [410000000, 525000000, 22, "410 - 525 MHz", "rnode_firmware_t3s3.zip", "SX1268"],
0xA6: [820000000, 1020000000, 22, "820 - 960 MHz", "rnode_firmware_t3s3.zip", "SX1262"],
0xA5: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_t3s3_sx127x.zip", "SX1278"],
0xAA: [820000000, 1020000000, 17, "820 - 960 MHz", "rnode_firmware_t3s3_sx127x.zip", "SX1276"],
0xAC: [2400000000, 2500000000, 20, "2.4 - 2.5 GHz", "rnode_firmware_t3s3_sx1280_pa.zip", "SX1280"],
0xA2: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng21.zip", "SX1278"],
0xA7: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware_ng21.zip", "SX1276"],
0xA3: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng20.zip", "SX1278"],
@@ -251,12 +298,25 @@ models = {
0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip", "SX1276"],
0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip", "SX1278"],
0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip", "SX1276"],
0xC5: [470000000, 510000000, 21, "470 - 510 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
0xCA: [863000000, 928000000, 21, "863 - 928 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
0xC5: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_heltec32v3.zip", "SX1268"],
0xCA: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
0xC6: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_heltec_t114.zip", "SX1268"],
0xC7: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_heltec_t114.zip", "SX1262"],
0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip", "SX1278"],
0xE9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_tbeam.zip", "SX1276"],
0xD4: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tdeck.zip", "SX1268"],
0xD9: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tdeck.zip", "SX1262"],
0xDB: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tbeam_supreme.zip", "SX1268"],
0xDC: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tbeam_supreme.zip", "SX1262"],
0xE3: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tbeam_sx1262.zip", "SX1268"],
0xE8: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tbeam_sx1262.zip", "SX1262"],
0x11: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
0x12: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
0x13: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631_sx1280.zip", "SX1262 + SX1280"],
0x14: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631_sx1280.zip", "SX1262 + SX1280"],
0x16: [779000000, 928000000, 22, "430 - 510 Mhz", "rnode_firmware_techo.zip", "SX1262"],
0x17: [779000000, 928000000, 22, "779 - 928 Mhz", "rnode_firmware_techo.zip", "SX1262"],
0x21: [820000000, 960000000, 22, "820 - 960 MHz", "rnode_firmware_opencom_xl.zip", "SX1262 + SX1280"],
0xFE: [100000000, 1100000000, 17, "(Band capabilities unknown)", None, "Unknown"],
0xFF: [100000000, 1100000000, 14, "(Band capabilities unknown)", None, "Unknown"],
}
@@ -620,6 +680,44 @@ class RNode():
if written != len(kiss_command):
raise IOError("An IO error occurred while sending display intensity command to device")
def set_display_blanking(self, blanking_timeout):
data = bytes([blanking_timeout & 0xFF])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_BLNK])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending display blanking timeout command to device")
def set_display_rotation(self, rotation):
data = bytes([rotation & 0xFF])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ROT])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending display rotation command to device")
def recondition_display(self):
data = bytes([0x01])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_RCND])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending display recondition command to device")
def set_disable_interference_avoidance(self, ia_disabled):
if ia_disabled:
data = bytes([0x01])
else:
data = bytes([0x00])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DIS_IA])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending interference avoidance configuration command to device")
def set_neopixel_intensity(self, intensity):
data = bytes([intensity & 0xFF])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_NP_INT])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending NeoPixel intensity command to device")
def set_display_address(self, address):
data = bytes([address & 0xFF])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ADR])+data+bytes([KISS.FEND])
@@ -1096,9 +1194,10 @@ def ensure_firmware_file(fw_filename):
RNS.log("The selected firmware for this board is version "+selected_version)
else:
RNS.log("Online firmware version check was disabled, but no firmware version specified for install.")
RNS.log("use the --fw-version option to manually specify a version.")
graceful_exit(98)
if selected_version == None:
RNS.log("Online firmware version check was disabled, but no firmware version specified for install.")
RNS.log("use the --fw-version option to manually specify a version.")
graceful_exit(98)
# if custom firmware url, use it
if fw_url != None:
@@ -1142,6 +1241,7 @@ def ensure_firmware_file(fw_filename):
pass
else:
RNS.log("")
RNS.log(f"Firmware hash {file_hash} but should be {selected_hash}, possibly due to download corruption.")
RNS.log("Firmware corrupt. Try clearing the local firmware cache with: rnodeconf --clear-cache")
graceful_exit(96)
@@ -1244,7 +1344,12 @@ def main():
parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode")
parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)")
parser.add_argument("-t", "--timeout", action="store", metavar="s", type=int, default=None, help="Set display timeout in seconds, 0 to disable")
parser.add_argument("-R", "--rotation", action="store", metavar="rotation", type=int, default=None, help="Set display rotation, valid values are 0 through 3")
parser.add_argument("--display-addr", action="store", metavar="byte", type=str, default=None, help="Set display address as hex byte (00 - FF)")
parser.add_argument("--recondition-display", action="store_true", help="Start display reconditioning")
parser.add_argument("--np", action="store", metavar="i", type=int, default=None, help="Set NeoPixel intensity (0-255)")
parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode")
parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode")
@@ -1252,6 +1357,11 @@ def main():
parser.add_argument("--sf", action="store", metavar="factor", type=int, default=None, help="Spreading factor for TNC mode (7 - 12)")
parser.add_argument("--cr", action="store", metavar="rate", type=int, default=None, help="Coding rate for TNC mode (5 - 8)")
parser.add_argument("-x", "--ia-enable", action="store_true", help="Enable interference avoidance")
parser.add_argument("-X", "--ia-disable", action="store_true", help="Disable interference avoidance")
parser.add_argument("-c", "--config", action="store_true", help="Print device configuration")
parser.add_argument("--eeprom-backup", action="store_true", help="Backup EEPROM to file")
parser.add_argument("--eeprom-dump", action="store_true", help="Dump EEPROM to console")
parser.add_argument("--eeprom-wipe", action="store_true", help="Unlock and wipe EEPROM")
@@ -1265,7 +1375,7 @@ def main():
parser.add_argument("-r", "--rom", action="store_true", help="Bootstrap EEPROM without flashing firmware")
parser.add_argument("-k", "--key", action="store_true", help="Generate a new signing key and exit") #
parser.add_argument("-S", "--sign", action="store_true", help="Display public part of signing key")
parser.add_argument("-H", "--firmware-hash", action="store", help="Display installed firmware hash")
parser.add_argument("-H", "--firmware-hash", action="store", help="Set installed firmware hash")
parser.add_argument("-K", "--get-target-firmware-hash", action="store_true", help=argparse.SUPPRESS) # Get target firmware hash from device
parser.add_argument("-L", "--get-firmware-hash", action="store_true", help=argparse.SUPPRESS) # Get calculated firmware hash from device
parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help="Platform specification for device bootstrap")
@@ -1585,27 +1695,31 @@ def main():
print("")
print("What kind of device is this?\n")
print("[1] A specific kind of RNode")
print(" .")
print(" / \\ Select this option if you have an RNode of a specific")
print(" | type, built from a recipe or bought from a vendor.")
print(" | Select this option if you have an RNode of a specific")
print(" \\ / type, built from a recipe or bought from a vendor.")
print(" '")
print("[1] A specific kind of RNode")
print("")
print("[2] Homebrew RNode")
print(" .")
print(" / \\ Select this option if you have put toghether an RNode")
print(" | of your own design, or if you are prototyping one.")
print(" | Select this option if you have put toghether an RNode")
print(" \\ / of your own design, or if you are prototyping one.")
print(" '")
print("[2] Homebrew RNode")
print("")
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
print("[4] LilyGO LoRa32 v2.0")
print("[5] LilyGO LoRa32 v1.0")
print("[6] LilyGO T-Beam")
print("[7] Heltec LoRa32 v2")
print("[8] Heltec LoRa32 v3")
print("[9] LilyGO LoRa T3S3")
print(" | Select one of these options if you want to easily turn")
print(" \\ / a supported development board into an RNode.")
print(" '")
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
print("[4] LilyGO LoRa32 v2.0")
print("[5] LilyGO LoRa32 v1.0")
print("[6] LilyGO T-Beam")
print("[7] Heltec LoRa32 v2")
print("[8] Heltec LoRa32 v3")
print("[9] LilyGO LoRa T3S3")
print("[10] RAK4631")
print(" .")
print(" / \\ Select one of these options if you want to easily turn")
print(" | a supported development board into an RNode.")
print("[11] LilyGo T-Echo")
print("[12] LilyGO T-Beam Supreme")
print("[13] LilyGO T-Deck")
print("[14] Heltec T114")
print("")
print("---------------------------------------------------------------------------")
print("\nEnter the number that matches your device type:\n? ", end="")
@@ -1614,7 +1728,7 @@ def main():
try:
c_dev = int(input())
c_mod = False
if c_dev < 1 or c_dev > 10:
if c_dev < 1 or c_dev > 14:
raise ValueError()
elif c_dev == 1:
selected_product = ROM.PRODUCT_RNODE
@@ -1651,6 +1765,38 @@ def main():
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 12:
selected_product = ROM.PRODUCT_TBEAM_S_V1
clear()
print("")
print("---------------------------------------------------------------------------")
print(" T-Beam Supreme RNode Installer")
print("")
print("The RNode firmware can currently be installed on T-Beam Supreme devices")
print("using the SX1262 and SX1268 transceiver chips.")
print("")
print("Important! Using RNode firmware on T-Beam devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 13:
selected_product = ROM.PRODUCT_TDECK
clear()
print("")
print("---------------------------------------------------------------------------")
print(" T-Deck RNode Installer")
print("")
print("The RNode firmware can currently be installed on T-Deck devices using the")
print("SX1262 and SX1268 transceiver chips.")
print("")
print("Important! Using RNode firmware on T-Beam devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 4:
selected_product = ROM.PRODUCT_T32_20
clear()
@@ -1721,8 +1867,6 @@ def main():
print("Important! Using RNode firmware on T3S3 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("")
print("Please note that Bluetooth is currently not implemented on this board.")
print("")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
@@ -1737,8 +1881,6 @@ def main():
print("Important! Using RNode firmware on Heltec devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("")
print("Please note that Bluetooth is currently not implemented on this board.")
print("")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
@@ -1756,6 +1898,33 @@ def main():
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 11:
selected_product = ROM.PRODUCT_TECHO
clear()
print("")
print("---------------------------------------------------------------------------")
print(" LilyGo T-Echo RNode Installer")
print("")
print("Important! Using RNode firmware on LilyGo T-Echo devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 14:
selected_product = ROM.PRODUCT_HELTEC_T114
clear()
print("")
print("---------------------------------------------------------------------------")
print(" Heltec T114 RNode Installer")
print("")
print("Important! Using RNode firmware on Heltec T114 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
except Exception as e:
print("That device type does not exist, exiting now.")
graceful_exit()
@@ -1858,22 +2027,39 @@ def main():
print("That model does not exist, exiting now.")
graceful_exit()
else:
print("\nWhat model is this T3S3?\n")
print("[1] 410 - 525 MHz (with SX1268 chip)")
print("[2] 820 - 1020 MHz (with SX1262 chip)")
print("\nWhat band is this T3S3 for?\n")
print("[1] 433 MHz (with SX1278 chip)")
print("[2] 868/915/923 MHz (with SX1276 chip)")
print("");
print("[3] 433 MHz (with SX1268 chip)")
print("[4] 868/915/923 MHz (with SX1262 chip)")
print("");
print("[5] 2.4 GHz (with SX1280 chip and PA)")
print("\n? ", end="")
try:
c_model = int(input())
if c_model < 1 or c_model > 2:
if c_model < 1 or c_model > 5:
raise ValueError()
elif c_model == 1:
selected_model = ROM.MODEL_A1
selected_model = ROM.MODEL_A5
selected_mcu = ROM.MCU_ESP32
selected_platform = ROM.PLATFORM_ESP32
elif c_model == 2:
selected_model = ROM.MODEL_AA
selected_mcu = ROM.MCU_ESP32
selected_platform = ROM.PLATFORM_ESP32
elif c_model == 3:
selected_model = ROM.MODEL_A1
selected_mcu = ROM.MCU_ESP32
selected_platform = ROM.PLATFORM_ESP32
elif c_model == 4:
selected_model = ROM.MODEL_A6
selected_mcu = ROM.MCU_ESP32
selected_platform = ROM.PLATFORM_ESP32
elif c_model == 5:
selected_model = ROM.MODEL_AC
selected_mcu = ROM.MCU_ESP32
selected_platform = ROM.PLATFORM_ESP32
except Exception as e:
print("That model does not exist, exiting now.")
graceful_exit()
@@ -1907,6 +2093,46 @@ def main():
print("That band does not exist, exiting now.")
graceful_exit()
elif selected_product == ROM.PRODUCT_TBEAM_S_V1:
selected_mcu = ROM.MCU_ESP32
print("\nWhat band is this T-Beam Supreme for?\n")
print("[1] 433 MHz (with SX1268 chip)")
print("[2] 868/915/923 MHz (with SX1262 chip)")
print("\n? ", end="")
try:
c_model = int(input())
if c_model < 1 or c_model > 2:
raise ValueError()
elif c_model == 1:
selected_model = ROM.MODEL_DB
selected_platform = ROM.PLATFORM_ESP32
elif c_model == 2:
selected_model = ROM.MODEL_DC
selected_platform = ROM.PLATFORM_ESP32
except Exception as e:
print("That band does not exist, exiting now.")
graceful_exit()
elif selected_product == ROM.PRODUCT_TDECK:
selected_mcu = ROM.MCU_ESP32
print("\nWhat band is this T-Deck for?\n")
print("[1] 433 MHz (with SX1268 chip)")
print("[2] 868/915/923 MHz (with SX1262 chip)")
print("\n? ", end="")
try:
c_model = int(input())
if c_model < 1 or c_model > 2:
raise ValueError()
elif c_model == 1:
selected_model = ROM.MODEL_D4
selected_platform = ROM.PLATFORM_ESP32
elif c_model == 2:
selected_model = ROM.MODEL_D9
selected_platform = ROM.PLATFORM_ESP32
except Exception as e:
print("That band does not exist, exiting now.")
graceful_exit()
elif selected_product == ROM.PRODUCT_T32_10:
selected_mcu = ROM.MCU_ESP32
print("\nWhat band is this LoRa32 for?\n")
@@ -2021,6 +2247,28 @@ def main():
except Exception as e:
print("That band does not exist, exiting now.")
exit()
elif selected_product == ROM.PRODUCT_HELTEC_T114:
selected_mcu = ROM.MCU_NRF52
print("\nWhat band is this Heltec T114 for?\n")
print("[1] 433 MHz")
print("[2] 868 MHz")
print("[3] 915 MHz")
print("[4] 923 MHz")
try:
c_model = int(input())
if c_model < 1 or c_model > 4:
raise ValueError()
elif c_model == 1:
selected_model = ROM.MODEL_C6
selected_platform = ROM.PLATFORM_NRF52
elif c_model > 1:
selected_model = ROM.MODEL_C7
selected_platform = ROM.PLATFORM_NRF52
except Exception as e:
print("That band does not exist, exiting now.")
exit()
elif selected_product == ROM.PRODUCT_RAK4631:
selected_mcu = ROM.MCU_NRF52
print("\nWhat band is this RAK4631 for?\n")
@@ -2042,6 +2290,27 @@ def main():
except Exception as e:
print("That band does not exist, exiting now.")
graceful_exit()
elif selected_product == ROM.PRODUCT_TECHO:
selected_mcu = ROM.MCU_NRF52
print("\nWhat band is this T-Echo for?\n")
print("[1] 433 MHz")
print("[2] 868 MHz")
print("[3] 915 MHz")
print("[4] 923 MHz")
print("\n? ", end="")
try:
c_model = int(input())
if c_model < 1 or c_model > 4:
raise ValueError()
elif c_model == 1:
selected_model = ROM.MODEL_16
selected_platform = ROM.PLATFORM_NRF52
elif c_model > 1:
selected_model = ROM.MODEL_17
selected_platform = ROM.PLATFORM_NRF52
except Exception as e:
print("That band does not exist, exiting now.")
graceful_exit()
if selected_model != ROM.MODEL_FF and selected_model != ROM.MODEL_FE:
fw_filename = models[selected_model][4]
@@ -2554,6 +2823,7 @@ def main():
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.boot_app0",
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.partitions",
]
elif fw_filename == "rnode_firmware_featheresp32.zip":
@@ -2718,6 +2988,78 @@ def main():
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3.partitions",
]
elif fw_filename == "rnode_firmware_t3s3_sx127x.zip":
return [
sys.executable, flasher,
"--chip", "esp32s3",
"--port", args.port,
"--baud", args.baud_flash,
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.boot_app0",
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.partitions",
]
elif fw_filename == "rnode_firmware_t3s3_sx1280_pa.zip":
return [
sys.executable, flasher,
"--chip", "esp32s3",
"--port", args.port,
"--baud", args.baud_flash,
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.boot_app0",
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.partitions",
]
elif fw_filename == "rnode_firmware_tbeam_supreme.zip":
return [
sys.executable, flasher,
"--chip", "esp32s3",
"--port", args.port,
"--baud", args.baud_flash,
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.boot_app0",
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.partitions",
]
elif fw_filename == "rnode_firmware_tdeck.zip":
return [
sys.executable, flasher,
"--chip", "esp32s3",
"--port", args.port,
"--baud", args.baud_flash,
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.boot_app0",
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.partitions",
]
elif fw_filename == "extracted_rnode_firmware.zip":
return [
sys.executable, flasher,
@@ -2831,12 +3173,12 @@ def main():
if args.platform == ROM.PLATFORM_ESP32:
wants_fw_provision = True
RNS.log("Waiting for ESP32 reset...")
time.sleep(7)
time.sleep(8)
if args.platform == ROM.PLATFORM_NRF52:
wants_fw_provision = True
RNS.log("Waiting for NRF52 reset...")
# Don't need to wait as long this time.
time.sleep(5)
time.sleep(6)
else:
RNS.log("Error from flasher ("+str(flash_status)+") while writing.")
RNS.log("Some boards have trouble flashing at high speeds, and you can")
@@ -3088,6 +3430,63 @@ def main():
RNS.log("Firmware update file not found")
graceful_exit()
if args.config:
eeprom_reserved = 200
if rnode.platform == ROM.PLATFORM_ESP32:
eeprom_size = 296
elif rnode.platform == ROM.PLATFORM_NRF52:
eeprom_size = 296
else:
eeprom_size = 4096
eeprom_offset = eeprom_size-eeprom_reserved
def ea(a):
return a+eeprom_offset
ec_bt = rnode.eeprom[ROM.ADDR_CONF_BT]
ec_dint = rnode.eeprom[ROM.ADDR_CONF_DINT]
ec_dadr = rnode.eeprom[ROM.ADDR_CONF_DADR]
ec_dblk = rnode.eeprom[ROM.ADDR_CONF_DBLK]
ec_drot = rnode.eeprom[ROM.ADDR_CONF_DROT]
ec_pset = rnode.eeprom[ROM.ADDR_CONF_PSET]
ec_pint = rnode.eeprom[ROM.ADDR_CONF_PINT]
ec_bset = rnode.eeprom[ROM.ADDR_CONF_BSET]
ec_dia = rnode.eeprom[ROM.ADDR_CONF_DIA]
print("\nDevice configuration:")
if ec_bt == 0x73:
print(f" Bluetooth : Enabled")
else:
print(f" Bluetooth : Disabled")
if ec_dia == 0x00:
print(f" Interference avoidance : Enabled")
else:
print(f" Interference avoidance : Disabled")
print( f" Display brightness : {ec_dint}")
if ec_dadr == 0xFF:
print(f" Display address : Default")
else:
print(f" Display address : {RNS.hexrep(ec_dadr, delimit=False)}")
if ec_bset == 0x73 and ec_dblk != 0x00:
print(f" Display blanking : {ec_dblk}s")
else:
print(f" Display blanking : Disabled")
if ec_drot != 0xFF:
if ec_drot == 0x00:
rstr = "Landscape"
if ec_drot == 0x01:
rstr = "Portrait"
if ec_drot == 0x02:
rstr = "Landscape 180"
if ec_drot == 0x03:
rstr = "Portrait 180"
print(f" Display rotation : {rstr}")
else:
print(f" Display rotation : Default")
if ec_pset == 0x73:
print(f" Neopixel Intensity : {ec_pint}")
print("")
graceful_exit()
if args.eeprom_dump:
RNS.log("EEPROM contents:")
RNS.log(RNS.hexrep(rnode.eeprom))
@@ -3116,6 +3515,52 @@ def main():
RNS.log("Setting display intensity to "+str(di))
rnode.set_display_intensity(di)
if isinstance(args.timeout, int):
di = args.timeout
if di < 0:
di = 0
if di > 255:
di = 255
if di == 0:
RNS.log("Disabling display blanking")
else:
RNS.log("Setting display timeout to "+str(di))
rnode.set_display_blanking(di)
if isinstance(args.rotation, int):
dr = args.rotation
if dr < 0:
dr = 0
if dr > 3:
dr = 3
RNS.log("Setting display rotation to "+str(dr))
rnode.set_display_rotation(dr)
if isinstance(args.recondition_display, bool):
if args.recondition_display:
RNS.log("Starting display reconditioning")
rnode.recondition_display()
if isinstance(args.ia_enable, bool):
if args.ia_enable:
RNS.log("Enabling interference avoidance")
rnode.set_disable_interference_avoidance(False)
if isinstance(args.ia_disable, bool):
if args.ia_disable:
RNS.log("Disabling interference avoidance")
rnode.set_disable_interference_avoidance(True)
if isinstance(args.np, int):
di = args.np
if di < 0:
di = 0
if di > 255:
di = 255
RNS.log("Setting NeoPixel intensity to "+str(di))
rnode.set_neopixel_intensity(di)
if isinstance(args.display_addr, str):
set_addr = False
try:
@@ -3228,7 +3673,7 @@ def main():
elif rnode.platform == ROM.PLATFORM_NRF52:
rnode_serial.close()
RNS.log("Waiting for NRF52 reset...")
time.sleep(14)
time.sleep(18)
selected_port = None
ports = list_ports.comports()
for port in ports:
@@ -3439,7 +3884,9 @@ def main():
if rnode.platform == ROM.PLATFORM_ESP32:
rnode.hard_reset()
RNS.log("Waiting for ESP32 reset...")
time.sleep(6.5)
time.sleep(7)
if selected_model in [ROM.MODEL_AC, ROM.MODEL_A6, ROM.MODEL_A1, ROM.MODEL_AA, ROM.MODEL_A5]:
time.sleep(5)
elif rnode.platform == ROM.PLATFORM_NRF52:
# Wait a few seconds before hard resetting.
@@ -3458,7 +3905,7 @@ def main():
# Give plenty of time for to allow for
# potential e-ink display refresh too.
time.sleep(14)
time.sleep(20)
# After the hard reset, the port number will
# change. We need to find the new port number,
+31
View File
@@ -42,6 +42,8 @@ def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
if reticulum.is_connected_to_shared_instance:
RNS.log("Started rnsd version {version} connected to another shared local instance, this is probably NOT what you want!".format(version=__version__), RNS.LOG_WARNING)
else:
if RNS.Reticulum.get_instance().shared_instance_interface:
RNS.Reticulum.get_instance().shared_instance_interface.server.daemon_threads = True
RNS.log("Started rnsd version {version}".format(version=__version__), RNS.LOG_NOTICE)
while True:
@@ -123,6 +125,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 +296,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
+97 -11
View File
@@ -135,8 +135,26 @@ def get_remote_status(destination_hash, include_lstats, identity, no_output=Fals
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False,
lstats=False, sorting=None, sort_reverse=False, remote=None, management_identity=None,
remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, must_exit=True, rns_instance=None, traffic_totals=False):
if remote:
require_shared = False
else:
require_shared = True
try:
if rns_instance:
reticulum = rns_instance
must_exit = False
else:
reticulum = RNS.Reticulum(configdir=configdir, loglevel=3+verbosity, require_shared_instance=require_shared)
except Exception as e:
print("No shared RNS instance available to get status from")
if must_exit:
exit(1)
else:
return
link_count = None
stats = None
@@ -164,7 +182,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
except Exception as e:
print(str(e))
exit(20)
if must_exit:
exit(20)
else:
return
else:
if lstats:
@@ -193,7 +214,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
i[k] = RNS.hexrep(i[k], delimit=False)
print(json.dumps(stats))
exit()
if must_exit:
exit()
else:
return
interfaces = stats["interfaces"]
if sorting != None and isinstance(sorting, str):
@@ -204,6 +228,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
interfaces.sort(key=lambda i: i["rxb"], reverse=not sort_reverse)
if sorting == "tx":
interfaces.sort(key=lambda i: i["txb"], reverse=not sort_reverse)
if sorting == "rxs":
interfaces.sort(key=lambda i: i["rxs"], reverse=not sort_reverse)
if sorting == "txs":
interfaces.sort(key=lambda i: i["txs"], reverse=not sort_reverse)
if sorting == "traffic":
interfaces.sort(key=lambda i: i["rxb"]+i["txb"], reverse=not sort_reverse)
if sorting == "announces" or sorting == "announce":
@@ -292,12 +320,26 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "noise_floor" in ifstat:
if ifstat["noise_floor"] != None:
print(" Noise Fl. : {nfl} dBm".format(nfl=str(ifstat["noise_floor"])))
else:
print(" Noise Fl. : Unknown")
if "battery_percent" in ifstat and ifstat["battery_percent"] != None:
try:
bpi = int(ifstat["battery_percent"])
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"])))
if "channel_load_short" in ifstat and "channel_load_long" in ifstat:
print(" Ch.Load : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
print(" Ch. Load : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
@@ -329,7 +371,21 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
print(" Announces : {iaf}".format(iaf=RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])))
print(" {iaf}".format(iaf=RNS.prettyfrequency(ifstat["incoming_announce_frequency"])))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
rxb_str = ""+RNS.prettysize(ifstat["rxb"])
txb_str = ""+RNS.prettysize(ifstat["txb"])
strdiff = len(rxb_str)-len(txb_str)
if strdiff > 0:
txb_str += " "*strdiff
elif strdiff < 0:
rxb_str += " "*-strdiff
rxstat = rxb_str
txstat = txb_str
if "rxs" in ifstat and "txs" in ifstat:
rxstat += " "+RNS.prettyspeed(ifstat["rxs"])
txstat += " "+RNS.prettyspeed(ifstat["txs"])
print(f" Traffic : {txstat}\n {rxstat}")
lstr = ""
if link_count != None and lstats:
@@ -339,6 +395,19 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
else:
lstr = f" {link_count} entr{ms} in link table"
if traffic_totals:
rxb_str = ""+RNS.prettysize(stats["rxb"])
txb_str = ""+RNS.prettysize(stats["txb"])
strdiff = len(rxb_str)-len(txb_str)
if strdiff > 0:
txb_str += " "*strdiff
elif strdiff < 0:
rxb_str += " "*-strdiff
rxstat = rxb_str+" "+RNS.prettyspeed(stats["rxs"])
txstat = txb_str+" "+RNS.prettyspeed(stats["txs"])
print(f"\n Totals : {txstat}\n {rxstat}")
if "transport_id" in stats and stats["transport_id"] != None:
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
if "probe_responder" in stats and stats["probe_responder"] != None:
@@ -356,9 +425,12 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
print("Could not get RNS status")
else:
print("Could not get RNS status from remote transport instance "+RNS.prettyhexrep(identity_hash))
exit(1)
if must_exit:
exit(2)
else:
return
def main():
def main(must_exit=True, rns_instance=None):
try:
parser = argparse.ArgumentParser(description="Reticulum Network Stack Status")
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
@@ -388,11 +460,19 @@ def main():
default=False,
)
parser.add_argument(
"-t",
"--totals",
action="store_true",
help="display traffic totals",
default=False,
)
parser.add_argument(
"-s",
"--sort",
action="store",
help="sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]",
help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, held]",
default=None,
type=str
)
@@ -464,11 +544,17 @@ def main():
remote=args.R,
management_identity=args.i,
remote_timeout=args.w,
must_exit=must_exit,
rns_instance=rns_instance,
traffic_totals=args.totals,
)
except KeyboardInterrupt:
print("")
exit()
if must_exit:
exit()
else:
return
def speed_str(num, suffix='bps'):
units = ['','k','M','G','T','P','E','Z']
+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__
+282 -39
View File
@@ -24,6 +24,7 @@ import os
import sys
import glob
import time
import datetime
import random
import threading
@@ -43,8 +44,10 @@ from .Resource import Resource, ResourceAdvertisement
from .Cryptography import HKDF
from .Cryptography import Hashes
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
LOG_CRITICAL = 0
LOG_ERROR = 1
@@ -57,13 +60,16 @@ LOG_EXTREME = 7
LOG_STDOUT = 0x91
LOG_FILE = 0x92
LOG_CALLBACK = 0x93
LOG_MAXSIZE = 5*1024*1024
loglevel = LOG_NOTICE
logfile = None
logdest = LOG_STDOUT
logcall = None
logtimefmt = "%Y-%m-%d %H:%M:%S"
logtimefmt_p = "%H:%M:%S.%f"
compact_log_fmt = False
instance_random = random.Random()
@@ -75,21 +81,21 @@ logging_lock = threading.Lock()
def loglevelname(level):
if (level == LOG_CRITICAL):
return "Critical"
return "[Critical]"
if (level == LOG_ERROR):
return "Error"
return "[Error] "
if (level == LOG_WARNING):
return "Warning"
return "[Warning] "
if (level == LOG_NOTICE):
return "Notice"
return "[Notice] "
if (level == LOG_INFO):
return "Info"
return "[Info] "
if (level == LOG_VERBOSE):
return "Verbose"
return "[Verbose] "
if (level == LOG_DEBUG):
return "Debug"
return "[Debug] "
if (level == LOG_EXTREME):
return "Extra"
return "[Extra] "
return "Unknown"
@@ -104,40 +110,51 @@ def timestamp_str(time_s):
timestamp = time.localtime(time_s)
return time.strftime(logtimefmt, timestamp)
def log(msg, level=3, _override_destination = False):
def precise_timestamp_str(time_s):
return datetime.datetime.now().strftime(logtimefmt_p)[:-3]
def log(msg, level=3, _override_destination = False, pt=False):
global _always_override_destination, compact_log_fmt
msg = str(msg)
if loglevel >= level:
if not compact_log_fmt:
logstring = "["+timestamp_str(time.time())+"] ["+loglevelname(level)+"] "+msg
if pt:
logstring = "["+precise_timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
else:
logstring = "["+timestamp_str(time.time())+"] "+msg
if not compact_log_fmt:
logstring = "["+timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
else:
logstring = "["+timestamp_str(time.time())+" "+msg
logging_lock.acquire()
with logging_lock:
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
print(logstring)
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
print(logstring)
logging_lock.release()
elif (logdest == LOG_FILE and logfile != None):
try:
file = open(logfile, "a")
file.write(logstring+"\n")
file.close()
if os.path.getsize(logfile) > LOG_MAXSIZE:
prevfile = logfile+".1"
if os.path.isfile(prevfile):
os.unlink(prevfile)
os.rename(logfile, prevfile)
elif (logdest == LOG_FILE and logfile != None):
try:
file = open(logfile, "a")
file.write(logstring+"\n")
file.close()
if os.path.getsize(logfile) > LOG_MAXSIZE:
prevfile = logfile+".1"
if os.path.isfile(prevfile):
os.unlink(prevfile)
os.rename(logfile, prevfile)
except Exception as e:
_always_override_destination = True
log("Exception occurred while writing log message to log file: "+str(e), LOG_CRITICAL)
log("Dumping future log events to console!", LOG_CRITICAL)
log(msg, level)
logging_lock.release()
except Exception as e:
logging_lock.release()
_always_override_destination = True
log("Exception occurred while writing log message to log file: "+str(e), LOG_CRITICAL)
log("Dumping future log events to console!", LOG_CRITICAL)
log(msg, level)
elif logdest == LOG_CALLBACK:
try:
logcall(logstring)
except Exception as e:
_always_override_destination = True
log("Exception occurred while calling external log handler: "+str(e), LOG_CRITICAL)
log("Dumping future log events to console!", LOG_CRITICAL)
log(msg, level)
def rand():
@@ -218,6 +235,11 @@ def prettydistance(m, suffix="m"):
return "%.2f %s%s" % (num, last_unit, suffix)
def prettytime(time, verbose=False, compact=False):
neg = False
if time < 0:
time = abs(time)
neg = True
days = int(time // (24 * 3600))
time = time % (24 * 3600)
hours = int(time // 3600)
@@ -268,7 +290,64 @@ def prettytime(time, verbose=False, compact=False):
if tstr == "":
return "0s"
else:
return tstr
if not neg:
return tstr
else:
return f"-{tstr}"
def prettyshorttime(time, verbose=False, compact=False):
neg = False
time = time*1e6
if time < 0:
time = abs(time)
neg = True
seconds = int(time // 1e6); time %= 1e6
milliseconds = int(time // 1e3); time %= 1e3
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:
if not neg:
return tstr
else:
return f"-{tstr}"
def phyparams():
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
@@ -282,6 +361,170 @@ def phyparams():
def panic():
os._exit(255)
exit_called = False
def exit():
print("")
sys.exit(0)
global exit_called
if not exit_called:
exit_called = True
print("")
Reticulum.exit_handler()
os._exit(0)
class Profiler:
_ran = False
profilers = {}
tags = {}
@staticmethod
def get_profiler(tag=None, super_tag=None):
if tag in Profiler.profilers:
return Profiler.profilers[tag]
else:
profiler = Profiler(tag, super_tag)
Profiler.profilers[tag] = profiler
return profiler
def __init__(self, tag=None, super_tag=None):
self.paused = False
self.pause_time = 0
self.pause_started = None
self.tag = tag
self.super_tag = super_tag
if self.super_tag in Profiler.profilers:
self.super_profiler = Profiler.profilers[self.super_tag]
self.pause_super = self.super_profiler.pause
self.resume_super = self.super_profiler.resume
else:
def noop(self=None):
pass
self.super_profiler = None
self.pause_super = noop
self.resume_super = noop
def __enter__(self):
self.pause_super()
tag = self.tag
super_tag = self.super_tag
thread_ident = threading.get_ident()
if not tag in Profiler.tags:
Profiler.tags[tag] = {"threads": {}, "super": super_tag}
if not thread_ident in Profiler.tags[tag]["threads"]:
Profiler.tags[tag]["threads"][thread_ident] = {"current_start": None, "captures": []}
Profiler.tags[tag]["threads"][thread_ident]["current_start"] = time.perf_counter()
self.resume_super()
def __exit__(self, exc_type, exc_value, traceback):
self.pause_super()
tag = self.tag
super_tag = self.super_tag
end = time.perf_counter() - self.pause_time
self.pause_time = 0
thread_ident = threading.get_ident()
if tag in Profiler.tags and thread_ident in Profiler.tags[tag]["threads"]:
if Profiler.tags[tag]["threads"][thread_ident]["current_start"] != None:
begin = Profiler.tags[tag]["threads"][thread_ident]["current_start"]
Profiler.tags[tag]["threads"][thread_ident]["current_start"] = None
Profiler.tags[tag]["threads"][thread_ident]["captures"].append(end-begin)
if not Profiler._ran:
Profiler._ran = True
self.resume_super()
def pause(self, pause_started=None):
if not self.paused:
self.paused = True
self.pause_started = pause_started or time.perf_counter()
self.pause_super(self.pause_started)
def resume(self):
if self.paused:
self.pause_time += time.perf_counter() - self.pause_started
self.paused = False
self.resume_super()
@staticmethod
def ran():
return Profiler._ran
@staticmethod
def results():
from statistics import mean, median, stdev
results = {}
for 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 > 1:
thread_results = {
"count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": stdev(thread_captures)
}
elif sample_count == 1:
thread_results = {
"count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": None
}
tag_captures.extend(thread_captures)
sample_count = len(tag_captures)
if sample_count > 1:
tag_results = {
"name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": stdev(tag_captures)
}
elif sample_count == 1:
tag_results = {
"name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": None
}
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"]; median = tag["median"]; stdev = tag["stdev"]
print( f"{ind}{name}")
print( f"{ind} Samples : {count}")
if stdev != None:
print(f"{ind} Mean : {prettyshorttime(mean)}")
print(f"{ind} Median : {prettyshorttime(median)}")
print(f"{ind} St.dev. : {prettyshorttime(stdev)}")
print( f"{ind} Total : {prettyshorttime(mean*count)}")
print("")
print("\nProfiler results:\n")
for tag_name in results:
tag = results[tag_name]
if tag["super"] == None:
print_results_recursive(tag, results)
profile = Profiler.get_profiler
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.7.6"
__version__ = "0.9.2"
+4 -2
View File
@@ -1,5 +1,7 @@
import os
import glob
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
+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.7.x` release cycle aims at completing
- The current `0.8.x` release cycle aims at completing
- [ ] Hot-pluggable interface system
- [ ] External interface plugins
- [ ] Network-wide path balancing and multi-pathing
- [ ] Expanded hardware support
- [ ] Overhauling and updating the documentation
- [ ] Distributed Destination Naming System
- [ ] Create a standalone RNS Daemon app for Android
- [ ] Network-wide path balancing
- [ ] Add automatic retries to all use cases of the `Request` API
- [ ] A standalone RNS Daemon app for Android
- [ ] Addding automatic retries to all use cases of the `Request` API
- [ ] Performance and memory optimisations of the Python reference implementation
- [ ] Fixing bugs discovered while operating Reticulum systems and applications
@@ -35,17 +38,9 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
- Update announce description
- Add in-depth explanation of the IFAC system
- Software
- Update Sideband screenshots
- Update Sideband description
- Update NomadNet screenshots
- Update Sideband screenshots
- Installation
- [x] Add a *Reticulum On Raspberry Pi* section
- [x] Update *Reticulum On Android* section if necessary
- [x] Update Android install documentation.
- Update software descriptions and screenshots
- Communications hardware section
- Add information about RNode external displays.
- [x] Packet radio modems.
- Possibly add other relevant types here as well.
- Setup *Best Practices For...* / *Installation Examples* section.
- Home or office (example)
@@ -65,6 +60,8 @@ These efforts seek to broaden the universality of the Reticulum software and har
### Functionality
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
- Add support for user-supplied external interface drivers
- Add interface hot-plug and live up/down control to running instances
- Add automatic retries to all use cases of the `Request` API
- Network-wide path balancing
- Distributed Destination Naming System
@@ -82,10 +79,10 @@ These effors seek to make Reticulum easier to use and operate, and to expand the
### Interfaceability
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
- Filesystem interface
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
- More LoRa transceivers
- AT-compatible modems
- Filesystem interface
- Direct SDR Support
- Optical mediums
- IR Transceivers
@@ -105,7 +102,7 @@ The Reticulum ecosystem is enriched by several other software and hardware proje
This section lists, in no particular order, various important efforts that would be beneficial to the goals of Reticulum.
- The [RNode](https://unsigned.io/rnode/) project
- [ ] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
- [x] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
## Release History
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: 4a5b669a6756de77e9b7c6b7dc5d5702
config: 137f4f4730a0df8f9714240ecb465598
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

+15 -1
View File
@@ -125,4 +125,18 @@ interface to efficiently pass files of any size over a Reticulum :ref:`Link<api-
.. literalinclude:: ../../Examples/Filetransfer.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
.. _example-custominterface:
Custom Interfaces
=================
The *ExampleInterface* demonstrates creating custom interfaces for Reticulum.
Any number of custom interfaces can be loaded and utilised by Reticulum, and
will be fully on-par with natively included interfaces, including all supported
:ref:`interface modes<interfaces-modes>` and :ref:`common configuration options<interfaces-options>`.
.. literalinclude:: ../../Examples/ExampleInterface.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py>`_.
+221 -37
View File
@@ -27,9 +27,14 @@ and install them offline using ``pip``:
pip install ./rns-0.5.1-py3-none-any.whl
For more detailed installation instructions, please see the
:ref:`Platform-Specific Install Notes<install-guides>` section.
After installation is complete, it might be helpful to refer to the
:ref:`Using Reticulum on Your System<using-main>` chapter.
Resolving Dependency & Installation Issues
=============================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On some platforms, there may not be binary packages available for all dependencies, and
``pip`` installation may fail with an error message. In these cases, the issue can usually
be resolved by installing the development essentials packages for your platform:
@@ -100,10 +105,12 @@ You can install Nomad Network via pip:
# ... and run
nomadnet
**Please Note**: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again.
.. note::
If this is the very first time you use ``pip`` to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again. In some cases, you may even need to
manually add the ``pip`` install path to your ``PATH`` environment variable.
Sideband
^^^^^^^^
@@ -305,6 +312,20 @@ you are welcome to head over to the `GitHub discussion pages <https://github.com
and propose adding an interface for the hardware.
Creating and Using Custom Interfaces
===========================================
While Reticulum includes a flexible and broad range of built-in interfaces, these
will not cover every conceivable type of communications hardware that Reticulum
can potentially use to communicate.
It is therefore possible to easily write your own interface modules, that can be
loaded at run-time and used on-par with any of the built-in interface types.
For more information on this subject, and code examples to build on, please see
the :ref:`Configuring Interfaces<interfaces-main>` chapter.
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
@@ -318,14 +339,8 @@ The above command will install Reticulum and dependencies, and you will be
ready to import and use RNS in your own programs. The next step will most
likely be to look at some :ref:`Example Programs<examples-main>`.
For extended functionality, you can install optional dependencies:
.. code::
pip install pyserial
Further information can be found in the :ref:`API Reference<api-main>`.
The entire Reticulum API is documented in the :ref:`API Reference<api-main>`
chapter of this manual.
Participate in Reticulum Development
@@ -373,6 +388,7 @@ your first pull request, it is probably a good idea to introduce yourself on
the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussions>`_,
or ask one of the developers or maintainers for a good place to start.
.. _install-guides:
Platform-Specific Install Notes
==============================================
@@ -451,7 +467,7 @@ here at a later point. Until then you can use the `Sideband source code <https:/
ARM64
^^^^^^^^^^^^^^^^^^^^^^^^
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may need to install ``python3-dev`` before
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
installing Reticulum or programs that depend on Reticulum.
.. code::
@@ -463,16 +479,8 @@ installing Reticulum or programs that depend on Reticulum.
# Install Reticulum
python3 -m pip install rns
Raspberry Pi
^^^^^^^^^^^^^^^^^^^^^^^^^
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies.
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing some packages, and is not
detailed in this manual.
With these packages installed, ``pip`` will be able to build any missing dependencies
on your system locally.
Debian Bookworm
@@ -503,10 +511,152 @@ following section:
[global]
break-system-packages = true
Please note that the "break-system-packages" directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems.
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
option, you can use the following command:
.. code:: text
pip install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
MacOS
^^^^^^^^^^^^^^^^^^^^^^^^^
To install Reticulum on macOS, you will need to have Python and the ``pip`` package
manager installed.
Systems running macOS can vary quite widely in whether or not Python is pre-installed,
and if it is, which version is installed, and whether the ``pip`` package manager is
also installed and set up. If in doubt, you can `download and install <https://www.python.org/downloads/>`_
Python manually.
When Python and ``pip`` is available on your system, simply open a terminal window
and use one of the following commands:
.. code::
# Install Reticulum and utilities with pip:
pip3 install rns
# On some versions, you may need to use the
# flag --break-system-packages to install:
pip3 install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
Additionally, some version combinations of macOS and Python require you to
manually add your installed ``pip`` packages directory to your `PATH` environment
variable, before you can use installed commands in your terminal. Usually, adding
the following line to your shell init script (for example ``~/.zshrc``) will be enough:
.. code::
export PATH=$PATH:~/Library/Python/3.9/bin
Adjust Python version and shell init script location according to your system.
OpenWRT
^^^^^^^^^^^^^^^^^^^^^^^^^
On OpenWRT systems with sufficient storage and memory, you can install
Reticulum and related utilities using the `opkg` package manager and `pip`.
.. note::
At the time of releasing this manual, work is underway to create pre-built
Reticulum packages for OpenWRT, with full configuration, service
and ``uci`` integration. Please see the `feed-reticulum <https://github.com/gretel/feed-reticulum>`_
and `reticulum-openwrt <https://github.com/gretel/reticulum-openwrt>`_
repositories for more information.
To install Reticulum on OpenWRT, first log into a command line session, and
then use the following instructions:
.. code::
# Install dependencies
opkg install python3 python3-pip python3-cryptography python3-pyserial
# Install Reticulum
pip install rns
# Start rnsd with debug logging enabled
rnsd -vvv
.. note::
The above instructions have been verified and tested on OpenWRT 21.02 only.
It is likely that other versions may require slightly altered installation
commands or package names. You will also need enough free space in your
overlay FS, and enough free RAM to actually run Reticulum and any related
programs and utilities.
Depending on your device configuration, you may need to adjust firewall rules
for Reticulum connectivity to and from your device to work. Until proper
packaging is ready, you will also need to manually create a service or startup
script to automatically laucnh Reticulum at boot time.
Please also note that the `AutoInterface` requires link-local IPv6 addresses
to be enabled for any Ethernet and WiFi devices you intend to use. If ``ip a``
shows an address starting with ``fe80::`` for the device in question,
``AutoInterface`` should work for that device.
Raspberry Pi
^^^^^^^^^^^^^^^^^^^^^^^^^
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies. If Python and the
`pip` package manager is not already installed, do that first, and then
install Reticulum using `pip`.
.. code::
# Install dependencies
sudo apt install python3 python3-pip python3-cryptography python3-pyserial
# Install Reticulum
pip install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing required build dependencies,
and is not detailed in this manual.
RISC-V
^^^^^^^^^^^^^^^^^^^^^^^^
On some architectures, including RISC-V, not all dependencies have precompiled
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
installing Reticulum or programs that depend on Reticulum.
.. code::
# Install Python and development packages
sudo apt update
sudo apt install python3 python3-pip python3-dev
# Install Reticulum
python3 -m pip install rns
With these packages installed, ``pip`` will be able to build any missing dependencies
on your system locally.
Ubuntu Lunar
@@ -537,13 +687,52 @@ following section:
[global]
break-system-packages = true
Please note that the "break-system-packages" directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this _could_ in rare
cases lead to version conflicts, it does not generally pose any problems.
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
option, you can use the following command:
.. code:: text
pip install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
Windows
^^^^^^^^^^^^^^^^^^^^^^^^^
On Windows operating systems, the easiest way to install Reticulum is by using the
``pip`` package manager from the command line (either the command prompt or Windows
Powershell).
If you don't already have Python installed, `download and install Python <https://www.python.org/downloads/>`_.
At the time of publication of this manual, the recommended version is `Python 3.12.7 <https://www.python.org/downloads/release/python-3127>`_.
**Important!** When asked by the installer, make sure to add the Python program to
your PATH environment variables. If you don't do this, you will not be able to
use the ``pip`` installer, or run the included Reticulum utility programs (such as
``rnsd`` and ``rnstatus``) from the command line.
After installing Python, open the command prompt or Windows Powershell, and type:
.. code::
pip install rns
You can now use Reticulum and all included utility programs directly from your
preferred command line interface.
Pure-Python Reticulum
==============================================
.. warning::
If you use the ``rnspure`` package to run Reticulum on systems that
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
section of this manual.
In some rare cases, and on more obscure system types, it is not possible to
install one or more dependencies. In such situations,
you can use the ``rnspure`` package instead of the ``rns`` package, or use ``pip``
@@ -558,8 +747,3 @@ only if they are *needed* and *available*. If for example you want to use Reticu
on a system that cannot support ``pyserial``, it is perfectly possible to do so using
the `rnspure` package, but Reticulum will not be able to use serial-based interfaces.
All other available modules will still be loaded when needed.
**Please Note!** If you use the `rnspure` package to run Reticulum on systems that
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
section of this manual.
+122 -50
View File
@@ -75,8 +75,8 @@ completely from scratch, to your exact desired specifications, this chapter
will explain the easiest possible approach to creating RNodes: Using common
LoRa development boards. This approach can be boiled down to two simple steps:
1. Obtain one or more supported development boards
2. Install the RNode firmware with the automated installer
1. Obtain one or more :ref:`supported development boards<rnode-supported>`
2. Install the RNode firmware with the :ref:`automated installer<rnode-installation>`
Once the firmware has been installed and provisioned by the install script, it
is ready to use with any software that supports RNodes, including Reticulum.
@@ -85,82 +85,156 @@ to the configuration.
.. _rnode-supported:
Supported Boards
^^^^^^^^^^^^^^^^
Supported Boards and Devices
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To create one or more RNodes, you will need to obtain supported development
boards. The following boards are supported by the auto-installer.
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
+172 -82
View File
@@ -19,6 +19,16 @@ types, have a look at the :ref:`Building Networks<networks-main>` chapter of thi
manual.
.. _interfaces-custom:
Custom Interfaces
=================
In addition to the built-in interface types, Reticulum is **fully extensible** with
custom, user- or community-supplied interfaces, and creating custom interface
modules is straightforward. Please see the :ref:`custom interface<example-custominterface>`
example for basic interface code to build upon.
.. _interfaces-auto:
Auto Interface
@@ -152,11 +162,12 @@ It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.
**Please Note!** While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.
.. note::
While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.
It is important to note that the two methods are *interchangably compatible*.
You can use the I2PInterface to connect to a TCPServerInterface that
@@ -171,7 +182,7 @@ TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
the Internet or private IPv4 and IPv6 networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
@@ -200,8 +211,37 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
# device = eth0
# port = 4242
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
If you are using the interface on a device which has both IPv4 and IPv6 addresses available,
you can use the ``prefer_ipv6`` option to bind to the IPv6 address:
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
device = eth0
port = 4242
prefer_ipv6 = True
To use the TCP Server Interface over `Yggdrasil <https://yggdrasil-network.github.io/>`_, you
can simply specify the Yggdrasil ``tun`` device and a listening port, like so:
.. code::
[[Yggdrasil TCP Server Interface]]
type = TCPServerInterface
interface_enabled = yes
device = tun0
listen_port = 4343
.. note::
The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
@@ -231,7 +271,7 @@ and restore connectivity after a failure, once the other end of a TCP interface
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
# target_host can be a hostname or an IPv4 or IPv6 address.
[[TCP Client Interface]]
type = TCPClientInterface
@@ -239,6 +279,17 @@ and restore connectivity after a failure, once the other end of a TCP interface
target_host = 127.0.0.1
target_port = 4242
To use the TCP Client Interface over `Yggdrasil <https://yggdrasil-network.github.io/>`_, simply
specify the target Yggdrasil IPv6 address and port, like so:
.. code::
[[Yggdrasil TCP Client Interface]]
type = TCPClientInterface
interface_enabled = yes
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
target_port = 4343
It is also possible to use this interface type to connect via other programs
or hardware devices that expose a KISS interface on a TCP port, for example
software-based soundmodems. To do this, use the ``kiss_framing`` option:
@@ -262,8 +313,9 @@ never enable ``kiss_framing``, since this will disable internal reliability and
recovery mechanisms that greatly improves performance over unreliable and
intermittent TCP links.
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. note::
The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
@@ -285,11 +337,12 @@ private and the internet. It can also allow broadcast communication
over IP networks, so it can provide an easy way to enable connectivity
with all other peers on a local area network.
*Please Note!* Using broadcast UDP traffic has performance implications,
especially on WiFi. If your goal is simply to enable easy communication
with all peers in your local Ethernet broadcast domain, the
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
easier to use.
.. warning::
Using broadcast UDP traffic has performance implications,
especially on WiFi. If your goal is simply to enable easy communication
with all peers in your local Ethernet broadcast domain, the
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
easier to use.
.. code::
@@ -344,6 +397,11 @@ RNode LoRa Interface
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
can be used, and offers full control over LoRa parameters.
.. warning::
Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.
.. code::
# Here's an example of how to add a LoRa interface
@@ -358,6 +416,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
@@ -414,6 +489,11 @@ RNode Multi Interface
For RNodes that support multiple LoRa transceivers, the RNode
Multi interface can be used to configure sub-interfaces individually.
.. warning::
Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.
.. code::
# Here's an example of how to add an RNode Multi interface
@@ -437,89 +517,89 @@ Multi interface can be used to configure sub-interfaces individually.
# id_interval = 600
# A subinterface
[[[HIGHDATARATE]]]
# Subinterfaces can be enabled and disabled in of themselves
interface_enabled = True
[[[High Datarate]]]
# Subinterfaces can be enabled and disabled in of themselves
interface_enabled = True
# Set frequency to 2.4GHz
frequency = 2400000000
# Set frequency to 2.4GHz
frequency = 2400000000
# Set LoRa bandwidth to 1625 KHz
bandwidth = 1625000
# Set LoRa bandwidth to 1625 KHz
bandwidth = 1625000
# Set TX power to 0 dBm (0.12 mW)
txpower = 0
# Set TX power to 0 dBm (0.12 mW)
txpower = 0
# The virtual port, only the manufacturer
# or the person who wrote the board config
# can tell you what it will be for which
# physical hardware interface
vport = 1
# The virtual port, only the manufacturer
# or the person who wrote the board config
# can tell you what it will be for which
# physical hardware interface
vport = 1
# Select spreading factor 5. Valid
# range is 5 through 12, with 5
# being the fastest and 12 having
# the longest range.
spreadingfactor = 5
# Select spreading factor 5. Valid
# range is 5 through 12, with 5
# being the fastest and 12 having
# the longest range.
spreadingfactor = 5
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# It is possible to limit the airtime
# utilisation of an RNode by using the
# following two configuration options.
# The short-term limit is applied in a
# window of approximately 15 seconds,
# and the long-term limit is enforced
# over a rolling 60 minute window. Both
# options are specified in percent.
# It is possible to limit the airtime
# utilisation of an RNode by using the
# following two configuration options.
# The short-term limit is applied in a
# window of approximately 15 seconds,
# and the long-term limit is enforced
# over a rolling 60 minute window. Both
# options are specified in percent.
# airtime_limit_long = 100
# airtime_limit_short = 100
# airtime_limit_long = 100
# airtime_limit_short = 100
[[[LOWDATARATE]]]
# Subinterfaces can be enabled and disabled in of themselves
interface_enabled = True
[[[Low Datarate]]]
# Subinterfaces can be enabled and disabled in of themselves
interface_enabled = True
# Set frequency to 865.6 MHz
frequency = 865600000
# Set frequency to 865.6 MHz
frequency = 865600000
# The virtual port, only the manufacturer
# or the person who wrote the board config
# can tell you what it will be for which
# physical hardware interface
vport = 0
# The virtual port, only the manufacturer
# or the person who wrote the board config
# can tell you what it will be for which
# physical hardware interface
vport = 0
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 0 dBm (0.12 mW)
txpower = 0
# Set TX power to 0 dBm (0.12 mW)
txpower = 0
# Select spreading factor 7. Valid
# range is 5 through 12, with 5
# being the fastest and 12 having
# the longest range.
spreadingfactor = 7
# Select spreading factor 7. Valid
# range is 5 through 12, with 5
# being the fastest and 12 having
# the longest range.
spreadingfactor = 7
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# It is possible to limit the airtime
# utilisation of an RNode by using the
# following two configuration options.
# The short-term limit is applied in a
# window of approximately 15 seconds,
# and the long-term limit is enforced
# over a rolling 60 minute window. Both
# options are specified in percent.
# It is possible to limit the airtime
# utilisation of an RNode by using the
# following two configuration options.
# The short-term limit is applied in a
# window of approximately 15 seconds,
# and the long-term limit is enforced
# over a rolling 60 minute window. Both
# options are specified in percent.
# airtime_limit_long = 100
# airtime_limit_short = 100
# airtime_limit_long = 100
# airtime_limit_short = 100
.. _interfaces-serial:
@@ -581,6 +661,11 @@ radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
KISS interfaces can also be configured to periodically send out beacons
for station identification purposes.
.. warning::
Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.
.. code::
[[Packet Radio KISS Interface]]
@@ -644,6 +729,11 @@ encapsulate in AX.25.
A more efficient way is to use the plain KISS interface with the
beaconing functionality described above.
.. warning::
Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.
.. code::
[[Packet Radio AX.25 KISS Interface]]
+3 -1
View File
@@ -32,7 +32,9 @@ Provide Feedback
================
All feedback on the usage, functioning and potential dysfunctioning of any and
all components of the system is very valuable to the continued development and
improvement of Reticulum. Absolutely no automated analytics, telemetry, error
improvement of Reticulum.
Absolutely no automated analytics, telemetry, error
reporting or statistics is collected and reported by Reticulum under any
circumstances, so we rely on old-fashioned human feedback.
+31 -18
View File
@@ -134,10 +134,11 @@ be sufficient, even far into the future.
By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
channel to a destination, called a *Link*. Both data sent over Links and single packets offer
*Initiator Anonymity*, and links additionally offer *Forward Secrecy* by using an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
coordination, verification and reliability layers are fully autonomous and also based on elliptic
curve cryptography.
*Initiator Anonymity*. Links additionally offer *Forward Secrecy* by default, employing an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
layers are fully autonomous and also based on elliptic curve cryptography.
Reticulum also offers symmetric key encryption for group-oriented communications, as well as
unencrypted packets for local broadcast purposes.
@@ -431,7 +432,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
* | A packet is always created with an associated destination and some payload data. When the packet is sent
to a *single* destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the destination's public key, and encrypt the information.
an ECDH key exchange with the destination's public key (or ratchet key, if available), and encrypt the information.
* | It is important to note that this key exchange does not require any network traffic. The sender already
knows the public key of the destination from an earlier received *announce*, and can thus perform the ECDH
@@ -693,7 +694,8 @@ Wire Format
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
[Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
@@ -725,12 +727,16 @@ Wire Format
type 2 1 Two byte header, two 16 byte address fields
Context Flag
-----------------
unset 0 The context flag is used for various types
set 1 of signalling, depending on packet context
Propagation Types
-----------------
broadcast 00
transport 01
reserved 10
reserved 11
broadcast 0
transport 1
Destination Types
@@ -862,11 +868,17 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
* HKDF for key derivation
* Fernet for encrypted tokens
* Encrypted tokens are based on the Fernet spec
* AES-128 in CBC mode
* Ephemeral keys derived from an ECDH key exchange on Curve25519
* HMAC for message authentication
* AES-128 in CBC mode with PKCS7 padding
* HMAC using SHA256 for message authentication
* IVs are generated through os.urandom()
* No Fernet version and timestamp metadata fields
* SHA-256
@@ -876,12 +888,12 @@ In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
``Fernet`` primitives, and the ``PKCS7`` padding function are always provided by the
``Token`` primitives, and the ``PKCS7`` padding function are always provided by the
following internal implementations:
- ``RNS/Cryptography/HKDF.py``
- ``RNS/Cryptography/HMAC.py``
- ``RNS/Cryptography/Fernet.py``
- ``RNS/Cryptography/Token.py``
- ``RNS/Cryptography/PKCS7.py``
@@ -892,6 +904,7 @@ with the OpenSSL backend being *much* faster. The most important consequence how
potential loss of security by using primitives that has not seen the same amount of scrutiny,
testing and review as those from OpenSSL.
If you want to use the internal pure-python primitives, it is **highly advisable** that you
have a good understanding of the risks that this pose, and make an informed decision on whether
those risks are acceptable to you.
.. warning::
If you want to use the internal pure-python primitives, it is **highly advisable** that you
have a good understanding of the risks that this pose, and make an informed decision on whether
those risks are acceptable to you.
+94 -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
------------------------------
+47 -23
View File
@@ -17,7 +17,7 @@ Reticulum enables secure digital communication that cannot be subjected to
outside control, manipulation or censorship.
Reticulum enables the construction of both small and potentially planetary-scale
networks, without any need for hierarchical or beaureucratic structures to control
networks, without any need for hierarchical or bureaucratic structures to control
or manage them, while ensuring individuals and communities full sovereignty
over their own network segments.
@@ -43,19 +43,30 @@ considered complete and stable at the moment, but could change if absolutely war
What does Reticulum Offer?
==========================
* Coordination-less globally unique addressing and identification
* Fully self-configuring multi-hop routing
* Fully self-configuring multi-hop routing over heterogeneous carriers
* Complete initiator anonymity, communicate without revealing your identity
* Flexible scalability over heterogeneous topologies
* Asymmetric encryption based on X25519, and Ed25519 signatures as a basis for all communication
* Reticulum can carry data over any mixture of physical mediums and topologies
* Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
* Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
* Initiator anonymity, communicate without revealing your identity
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
* Reticulum does not include source addresses on any packets
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
* The foundational Reticulum Identity Keys are 512-bit Elliptic Curve keysets
* Forward Secrecy is available for all communication types, both for single packets and over links
* Reticulum uses the following format for encrypted tokens:
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
* AES-128 in CBC mode with PKCS7 padding
@@ -65,9 +76,31 @@ What does Reticulum Offer?
* Unforgeable packet delivery confirmations
* A variety of supported interface types
* Flexible and extensible interface system
* An intuitive and developer-friendly API
* Reticulum includes a large variety of built-in interface types
* Ability to load and utilise custom user- or community-supplied interface types
* Easily create your own custom interfaces for communicating over anything
* Authentication and virtual network segmentation on all supported interface types
* An intuitive and easy-to-use API
* Simpler and easier to use than sockets APIs and simpler, but more powerful
* Makes building distributed and decentralised applications much simpler
* Reliable and efficient transfer of arbitrary amounts of data
* Reticulum can handle a few bytes of data or files of many gigabytes
* Sequencing, compression, transfer coordination and checksumming are automatic
* The API is very easy to use, and provides transfer progress
* Lightweight, flexible and expandable Request/Response mechanism
* Efficient link establishment
@@ -75,17 +108,7 @@ What does Reticulum Offer?
* Low cost of keeping links open at only 0.44 bits per second
* Reliable and efficient transfer of arbitrary amounts of data
* Reticulum can handle a few bytes of data or files of many gigabytes
* Sequencing, transfer coordination and checksumming is automatic
* The API is very easy to use, and provides transfer progress
* Authentication and virtual network segmentation on all supported interface types
* Flexible scalability allowing extremely low-bandwidth networks to co-exist and interoperate with large, high-bandwidth networks
* Reliable sequential delivery with Channel and Buffer mechanisms
Where can Reticulum be Used?
@@ -99,7 +122,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
@@ -115,7 +139,7 @@ network, and vice versa.
Interface Types and Devices
===========================
Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, Reticulum can use the following devices and communication mediums:
Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it's simple to :ref:`implement an interface class<example-custominterface>`. Currently, Reticulum can use the following devices and communication mediums:
* Any Ethernet device
@@ -165,4 +189,4 @@ such. While it has been built with cryptography best-practices very foremost in
mind, it has not yet been externally security audited, and there could very well be
privacy-breaking bugs. To be considered secure, Reticulum needs a thorough
security review by independent cryptographers and security researchers. If you
want to help out with this, or can help sponsor an audit, please do get in touch.
want to help out with this, or can help sponsor an audit, please do get in touch.
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.7.6 beta',
VERSION: '0.9.2 beta',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+348 -71
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Code Examples - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Code Examples - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 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">
@@ -238,6 +238,7 @@ program.</p>
<span class="c1">##########################################################</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">RNS</span>
<span class="c1"># Let&#39;s define an app name. We&#39;ll use this for all</span>
@@ -331,7 +332,7 @@ program.</p>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Minimal.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Minimal.py</a>.</p>
@@ -349,6 +350,7 @@ notifications about announces from relevant destinations.</p>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">random</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">RNS</span>
<span class="c1"># Let&#39;s define an app name. We&#39;ll use this for all</span>
@@ -511,7 +513,7 @@ notifications about announces from relevant destinations.</p>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Announce.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Announce.py</a>.</p>
@@ -640,7 +642,7 @@ over the network.</p>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Broadcast.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Broadcast.py</a>.</p>
@@ -656,8 +658,8 @@ the Packet interface.</p>
<span class="c1"># of the packet. #</span>
<span class="c1">##########################################################</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">RNS</span>
<span class="c1"># Let&#39;s define an app name. We&#39;ll use this for all</span>
@@ -679,19 +681,8 @@ the Packet interface.</p>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Load identity from file if it exist or randomley create</span>
<span class="k">if</span> <span class="n">configpath</span><span class="p">:</span>
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">/storage/identitiesy/</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">configpath</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">/storage/identities/</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">configdir</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">):</span>
<span class="c1"># Load identity from file</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">from_file</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;loaded identity from file: &quot;</span><span class="o">+</span><span class="n">ifilepath</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Randomly create a new identity for our echo example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;created new identity&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our echo server</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="c1"># We create a destination that clients can query. We want</span>
<span class="c1"># to be able to verify echo replies to our clients, so we</span>
@@ -793,7 +784,7 @@ the Packet interface.</p>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -991,7 +982,7 @@ the Packet interface.</p>
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">timeoutarg</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Echo.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Echo.py</a>.</p>
@@ -1030,20 +1021,8 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Load identity from file if it exist or randomley create</span>
<span class="k">if</span> <span class="n">configpath</span><span class="p">:</span>
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">/storage/identitiesy/</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">configpath</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">/storage/identities/</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">configdir</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;ifilepath: </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">ifilepath</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">):</span>
<span class="c1"># Load identity from file</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">from_file</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;loaded identity from file: &quot;</span><span class="o">+</span><span class="n">ifilepath</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Randomly create a new identity for our link example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;created new identity&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our link example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="c1"># We create a destination that clients can connect to. We</span>
<span class="c1"># want clients to create links to this destination, so we</span>
@@ -1072,7 +1051,7 @@ destination, and passing traffic back and forth over the link.</p>
<span class="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C or </span><span class="se">\&quot;</span><span class="s2">quit</span><span class="se">\&quot;</span><span class="s2"> to quit)&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C to quit)&quot;</span><span class="p">)</span>
<span class="c1"># We enter a loop that runs until the users exits.</span>
<span class="c1"># If the user hits enter, we will announce our server</span>
@@ -1082,12 +1061,6 @@ destination, and passing traffic back and forth over the link.</p>
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Sent announce from &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
<span class="k">if</span> <span class="n">entered</span> <span class="o">==</span> <span class="s2">&quot;quit&quot;</span><span class="p">:</span>
<span class="k">if</span> <span class="n">latest_client_link</span><span class="p">:</span>
<span class="n">latest_client_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="c1"># When a client establishes a link to our server</span>
<span class="c1"># destination, this function will be called with</span>
@@ -1139,7 +1112,7 @@ destination, and passing traffic back and forth over the link.</p>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -1242,9 +1215,8 @@ destination, and passing traffic back and forth over the link.</p>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When a packet is received over the link, we</span>
<span class="c1"># simply print out the data.</span>
@@ -1308,7 +1280,7 @@ destination, and passing traffic back and forth over the link.</p>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Link.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Link.py</a>.</p>
@@ -1452,7 +1424,7 @@ the link has been established.</p>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -1564,9 +1536,8 @@ the link has been established.</p>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When a packet is received over the link, we</span>
<span class="c1"># simply print out the data.</span>
@@ -1630,7 +1601,7 @@ the link has been established.</p>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Identify.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Identify.py</a>.</p>
@@ -1759,7 +1730,7 @@ the link has been established.</p>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -1866,9 +1837,8 @@ the link has been established.</p>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1">##########################################################</span>
@@ -1924,7 +1894,7 @@ the link has been established.</p>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py</a>.</p>
@@ -2059,7 +2029,7 @@ data between peers of a <code class="docutils literal notranslate"><span class="
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
<span class="c1"># Let the user know that everything is ready</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Link example &quot;</span><span class="o">+</span>
<span class="s2">&quot;Channel example &quot;</span><span class="o">+</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
<span class="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</span>
@@ -2147,7 +2117,7 @@ data between peers of a <code class="docutils literal notranslate"><span class="
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -2211,7 +2181,7 @@ data between peers of a <code class="docutils literal notranslate"><span class="
<span class="n">packed_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">pack</span><span class="p">())</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">server_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
<span class="k">if</span> <span class="n">channel</span><span class="o">.</span><span class="n">is_ready_to_send</span><span class="p">():</span>
<span class="k">if</span> <span class="n">packed_size</span> <span class="o">&lt;=</span> <span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">:</span>
<span class="k">if</span> <span class="n">packed_size</span> <span class="o">&lt;=</span> <span class="n">channel</span><span class="o">.</span><span class="n">mdu</span><span class="p">:</span>
<span class="n">channel</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
@@ -2256,9 +2226,8 @@ data between peers of a <code class="docutils literal notranslate"><span class="
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When a packet is received over the channel, we</span>
<span class="c1"># simply print out the data.</span>
@@ -2322,7 +2291,7 @@ data between peers of a <code class="docutils literal notranslate"><span class="
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py</a>.</p>
@@ -2490,7 +2459,7 @@ binary data between peers of a <code class="docutils literal notranslate"><span
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -2587,9 +2556,8 @@ binary data between peers of a <code class="docutils literal notranslate"><span
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When the buffer has new data, read it and write it to the terminal.</span>
<span class="k">def</span> <span class="nf">client_buffer_ready</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
@@ -2653,7 +2621,7 @@ binary data between peers of a <code class="docutils literal notranslate"><span
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py</a>.</p>
@@ -2889,7 +2857,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -3127,7 +3095,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="k">global</span> <span class="n">server_files</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">server_files</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Timed out waiting for filelist, exiting&quot;</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When a link is closed, we&#39;ll inform the</span>
@@ -3140,9 +3108,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When RNS detects that the download has</span>
<span class="c1"># started, we&#39;ll update our menu state</span>
@@ -3266,11 +3233,320 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py</a>.</p>
</section>
<section id="custom-interfaces">
<span id="example-custominterface"></span><h2>Custom Interfaces<a class="headerlink" href="#custom-interfaces" title="Permalink to this heading">#</a></h2>
<p>The <em>ExampleInterface</em> demonstrates creating custom interfaces for Reticulum.
Any number of custom interfaces can be loaded and utilised by Reticulum, and
will be fully on-par with natively included interfaces, including all supported
<a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">interface modes</span></a> and <a class="reference internal" href="interfaces.html#interfaces-options"><span class="std std-ref">common configuration options</span></a>.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># MIT License - Copyright (c) 2024 Mark Qvist / unsigned.io</span>
<span class="c1"># This example illustrates creating a custom interface</span>
<span class="c1"># definition, that can be loaded and used by Reticulum at</span>
<span class="c1"># runtime. Any number of custom interfaces can be created</span>
<span class="c1"># and loaded. To use the interface place it in the folder</span>
<span class="c1"># ~/.reticulum/interfaces, and add an interface entry to</span>
<span class="c1"># your Reticulum configuration file similar to this:</span>
<span class="c1"># [[Example Custom Interface]]</span>
<span class="c1"># type = ExampleInterface</span>
<span class="c1"># enabled = no</span>
<span class="c1"># mode = gateway</span>
<span class="c1"># port = /dev/ttyUSB0</span>
<span class="c1"># speed = 115200</span>
<span class="c1"># databits = 8</span>
<span class="c1"># parity = none</span>
<span class="c1"># stopbits = 1</span>
<span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">sleep</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">threading</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="c1"># This HDLC helper class is used by the interface</span>
<span class="c1"># to delimit and packetize data over the physical</span>
<span class="c1"># medium - in this case a serial connection.</span>
<span class="k">class</span> <span class="nc">HDLC</span><span class="p">():</span>
<span class="c1"># This example interface packetizes data using</span>
<span class="c1"># simplified HDLC framing, similar to PPP</span>
<span class="n">FLAG</span> <span class="o">=</span> <span class="mh">0x7E</span>
<span class="n">ESC</span> <span class="o">=</span> <span class="mh">0x7D</span>
<span class="n">ESC_MASK</span> <span class="o">=</span> <span class="mh">0x20</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">escape</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">]),</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">,</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="o">^</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">]))</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">]),</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">,</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="o">^</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">]))</span>
<span class="k">return</span> <span class="n">data</span>
<span class="c1"># Let&#39;s define our custom interface class. It must</span>
<span class="c1"># be a sub-class of the RNS &quot;Interface&quot; class.</span>
<span class="k">class</span> <span class="nc">ExampleInterface</span><span class="p">(</span><span class="n">Interface</span><span class="p">):</span>
<span class="c1"># All interface classes must define a default</span>
<span class="c1"># IFAC size, used in IFAC setup when the user</span>
<span class="c1"># has not specified a custom IFAC size. This</span>
<span class="c1"># option is specified in bytes.</span>
<span class="n">DEFAULT_IFAC_SIZE</span> <span class="o">=</span> <span class="mi">8</span>
<span class="c1"># The following properties are local to this</span>
<span class="c1"># particular interface implementation.</span>
<span class="n">owner</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">port</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">speed</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">databits</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">parity</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">stopbits</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">serial</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># All Reticulum interfaces must have an __init__</span>
<span class="c1"># method that takes 2 positional arguments:</span>
<span class="c1"># The owner RNS Transport instance, and a dict</span>
<span class="c1"># of configuration values.</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">configuration</span><span class="p">):</span>
<span class="c1"># The following lines demonstrate handling</span>
<span class="c1"># potential dependencies required for the</span>
<span class="c1"># interface to function correctly.</span>
<span class="kn">import</span> <span class="nn">importlib</span>
<span class="k">if</span> <span class="n">importlib</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">find_spec</span><span class="p">(</span><span class="s1">&#39;serial&#39;</span><span class="p">)</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">serial</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Using this interface requires a serial communication module to be installed.&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_CRITICAL</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;You can install one with the command: python3 -m pip install pyserial&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_CRITICAL</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">panic</span><span class="p">()</span>
<span class="c1"># We start out by initialising the super-class</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span>
<span class="c1"># To make sure the configuration data is in the</span>
<span class="c1"># correct format, we parse it through the following</span>
<span class="c1"># method on the generic Interface class. This step</span>
<span class="c1"># is required to ensure compatibility on all the</span>
<span class="c1"># platforms that Reticulum supports.</span>
<span class="n">ifconf</span> <span class="o">=</span> <span class="n">Interface</span><span class="o">.</span><span class="n">get_config_obj</span><span class="p">(</span><span class="n">configuration</span><span class="p">)</span>
<span class="c1"># Read the interface name from the configuration</span>
<span class="c1"># and set it on our interface instance.</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">ifconf</span><span class="p">[</span><span class="s2">&quot;name&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
<span class="c1"># We read configuration parameters from the supplied</span>
<span class="c1"># configuration data, and provide default values in</span>
<span class="c1"># case any are missing.</span>
<span class="n">port</span> <span class="o">=</span> <span class="n">ifconf</span><span class="p">[</span><span class="s2">&quot;port&quot;</span><span class="p">]</span> <span class="k">if</span> <span class="s2">&quot;port&quot;</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="kc">None</span>
<span class="n">speed</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ifconf</span><span class="p">[</span><span class="s2">&quot;speed&quot;</span><span class="p">])</span> <span class="k">if</span> <span class="s2">&quot;speed&quot;</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="mi">9600</span>
<span class="n">databits</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ifconf</span><span class="p">[</span><span class="s2">&quot;databits&quot;</span><span class="p">])</span> <span class="k">if</span> <span class="s2">&quot;databits&quot;</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="mi">8</span>
<span class="n">parity</span> <span class="o">=</span> <span class="n">ifconf</span><span class="p">[</span><span class="s2">&quot;parity&quot;</span><span class="p">]</span> <span class="k">if</span> <span class="s2">&quot;parity&quot;</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="s2">&quot;N&quot;</span>
<span class="n">stopbits</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ifconf</span><span class="p">[</span><span class="s2">&quot;stopbits&quot;</span><span class="p">])</span> <span class="k">if</span> <span class="s2">&quot;stopbits&quot;</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="mi">1</span>
<span class="c1"># In case no port is specified, we abort setup by</span>
<span class="c1"># raising an exception.</span>
<span class="k">if</span> <span class="n">port</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;No port specified for </span><span class="si">{</span><span class="bp">self</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="c1"># All interfaces must supply a hardware MTU value</span>
<span class="c1"># to the RNS Transport instance. This value should</span>
<span class="c1"># be the maximum data packet payload size that the</span>
<span class="c1"># underlying medium is capable of handling in all</span>
<span class="c1"># cases without any segmentation.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">HW_MTU</span> <span class="o">=</span> <span class="mi">564</span>
<span class="c1"># We initially set the &quot;online&quot; property to false,</span>
<span class="c1"># since the interface has not actually been fully</span>
<span class="c1"># initialised and connected yet.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">False</span>
<span class="c1"># In this case, we can also set the indicated bit-</span>
<span class="c1"># rate of the interface to the serial port speed.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">bitrate</span> <span class="o">=</span> <span class="n">speed</span>
<span class="c1"># Configure internal properties on the interface</span>
<span class="c1"># according to the supplied configuration.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">pyserial</span> <span class="o">=</span> <span class="n">serial</span>
<span class="bp">self</span><span class="o">.</span><span class="n">serial</span> <span class="o">=</span> <span class="kc">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">owner</span>
<span class="bp">self</span><span class="o">.</span><span class="n">port</span> <span class="o">=</span> <span class="n">port</span>
<span class="bp">self</span><span class="o">.</span><span class="n">speed</span> <span class="o">=</span> <span class="n">speed</span>
<span class="bp">self</span><span class="o">.</span><span class="n">databits</span> <span class="o">=</span> <span class="n">databits</span>
<span class="bp">self</span><span class="o">.</span><span class="n">parity</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">PARITY_NONE</span>
<span class="bp">self</span><span class="o">.</span><span class="n">stopbits</span> <span class="o">=</span> <span class="n">stopbits</span>
<span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">100</span>
<span class="k">if</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;e&quot;</span> <span class="ow">or</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;even&quot;</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">parity</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">PARITY_EVEN</span>
<span class="k">if</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;o&quot;</span> <span class="ow">or</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;odd&quot;</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">parity</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">PARITY_ODD</span>
<span class="c1"># Since all required parameters are now configured,</span>
<span class="c1"># we will try opening the serial port.</span>
<span class="k">try</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">open_port</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Could not open serial port for interface &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">),</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="k">raise</span> <span class="n">e</span>
<span class="c1"># If opening the port succeeded, run any post-open</span>
<span class="c1"># configuration required.</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">is_open</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">configure_device</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">IOError</span><span class="p">(</span><span class="s2">&quot;Could not open serial port&quot;</span><span class="p">)</span>
<span class="c1"># Open the serial port with supplied configuration</span>
<span class="c1"># parameters and store a reference to the open port.</span>
<span class="k">def</span> <span class="nf">open_port</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Opening serial port &quot;</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="o">+</span><span class="s2">&quot;...&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">serial</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">pyserial</span><span class="o">.</span><span class="n">Serial</span><span class="p">(</span>
<span class="n">port</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="p">,</span>
<span class="n">baudrate</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">speed</span><span class="p">,</span>
<span class="n">bytesize</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">databits</span><span class="p">,</span>
<span class="n">parity</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parity</span><span class="p">,</span>
<span class="n">stopbits</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">stopbits</span><span class="p">,</span>
<span class="n">xonxoff</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
<span class="n">rtscts</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
<span class="n">timeout</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
<span class="n">inter_byte_timeout</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">write_timeout</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">dsrdtr</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># The only thing required after opening the port</span>
<span class="c1"># is to wait a small amount of time for the</span>
<span class="c1"># hardware to initialise and then start a thread</span>
<span class="c1"># that reads any incoming data from the device.</span>
<span class="k">def</span> <span class="nf">configure_device</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">read_loop</span><span class="p">)</span>
<span class="n">thread</span><span class="o">.</span><span class="n">daemon</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Serial port &quot;</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="o">+</span><span class="s2">&quot; is now open&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="c1"># This method will be called from our read-loop</span>
<span class="c1"># whenever a full packet has been received over</span>
<span class="c1"># the underlying medium.</span>
<span class="k">def</span> <span class="nf">process_incoming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
<span class="c1"># Update our received bytes counter</span>
<span class="bp">self</span><span class="o">.</span><span class="n">rxb</span> <span class="o">+=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="c1"># And send the data packet to the Transport</span>
<span class="c1"># instance for processing.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">owner</span><span class="o">.</span><span class="n">inbound</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span>
<span class="c1"># The running Reticulum Transport instance will</span>
<span class="c1"># call this method on the interface whenever the</span>
<span class="c1"># interface must transmit a packet.</span>
<span class="k">def</span> <span class="nf">process_outgoing</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">data</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">online</span><span class="p">:</span>
<span class="c1"># First, escape and packetize the data</span>
<span class="c1"># according to HDLC framing.</span>
<span class="n">data</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">])</span><span class="o">+</span><span class="n">HDLC</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">+</span><span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">])</span>
<span class="c1"># Then write the framed data to the port</span>
<span class="n">written</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="c1"># Update the transmitted bytes counter</span>
<span class="c1"># and ensure that all data was written</span>
<span class="bp">self</span><span class="o">.</span><span class="n">txb</span> <span class="o">+=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">written</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">IOError</span><span class="p">(</span><span class="s2">&quot;Serial interface only wrote &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">written</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; bytes of &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)))</span>
<span class="c1"># This read loop runs in a thread and continously</span>
<span class="c1"># receives bytes from the underlying serial port.</span>
<span class="c1"># When a full packet has been received, it will</span>
<span class="c1"># be sent to the process_incoming methed, which</span>
<span class="c1"># will in turn pass it to the Transport instance.</span>
<span class="k">def</span> <span class="nf">read_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">escape</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">data_buffer</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">last_read_ms</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span>
<span class="k">while</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">is_open</span><span class="p">:</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">in_waiting</span><span class="p">:</span>
<span class="n">byte</span> <span class="o">=</span> <span class="nb">ord</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="n">last_read_ms</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">in_frame</span> <span class="ow">and</span> <span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">):</span>
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">process_incoming</span><span class="p">(</span><span class="n">data_buffer</span><span class="p">)</span>
<span class="k">elif</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">):</span>
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">data_buffer</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="k">elif</span> <span class="p">(</span><span class="n">in_frame</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">data_buffer</span><span class="p">)</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">HW_MTU</span><span class="p">):</span>
<span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">):</span>
<span class="n">escape</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span><span class="n">escape</span><span class="p">):</span>
<span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span> <span class="o">^</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">):</span>
<span class="n">byte</span> <span class="o">=</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span>
<span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span> <span class="o">^</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">):</span>
<span class="n">byte</span> <span class="o">=</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span>
<span class="n">escape</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">data_buffer</span> <span class="o">=</span> <span class="n">data_buffer</span><span class="o">+</span><span class="nb">bytes</span><span class="p">([</span><span class="n">byte</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">time_since_last</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span> <span class="o">-</span> <span class="n">last_read_ms</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">data_buffer</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">time_since_last</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span><span class="p">:</span>
<span class="n">data_buffer</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;&quot;</span>
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">escape</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">sleep</span><span class="p">(</span><span class="mf">0.08</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;A serial port error occurred, the contained exception was: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The interface &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; experienced an unrecoverable error and is now offline.&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="k">if</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">panic_on_interface_error</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">panic</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Reticulum will attempt to reconnect the interface periodically.&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">reconnect_port</span><span class="p">()</span>
<span class="c1"># This method handles serial port disconnects.</span>
<span class="k">def</span> <span class="nf">reconnect_port</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">while</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">online</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Attempting to reconnect serial port &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; for &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot;...&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">open_port</span><span class="p">()</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">is_open</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">configure_device</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Error while reconnecting port, the contained exception was: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Reconnected serial port for &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span>
<span class="c1"># Signal to Reticulum that this interface should</span>
<span class="c1"># not perform any ingress limiting.</span>
<span class="k">def</span> <span class="nf">should_ingress_limit</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="c1"># We must provide a string representation of this</span>
<span class="c1"># interface, that is used whenever the interface</span>
<span class="c1"># is printed in logs or external programs.</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="s2">&quot;ExampleInterface[&quot;</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="o">+</span><span class="s2">&quot;]&quot;</span>
<span class="c1"># Finally, register the defined interface class as the</span>
<span class="c1"># target class for Reticulum to use as an interface</span>
<span class="n">interface_class</span> <span class="o">=</span> <span class="n">ExampleInterface</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py">https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py</a>.</p>
</section>
</section>
</article>
@@ -3340,6 +3616,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<li><a class="reference internal" href="#channel">Channel</a></li>
<li><a class="reference internal" href="#buffer">Buffer</a></li>
<li><a class="reference internal" href="#filetransfer">Filetransfer</a></li>
<li><a class="reference internal" href="#custom-interfaces">Custom Interfaces</a></li>
</ul>
</li>
</ul>
+4 -4
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 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">
+44 -10
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.7.6 beta documentation</title>
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -139,7 +139,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -165,7 +165,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -293,6 +293,8 @@
<li><a href="reference.html#RNS.Buffer.create_reader">create_reader() (RNS.Buffer static method)</a>
</li>
<li><a href="reference.html#RNS.Buffer.create_writer">create_writer() (RNS.Buffer static method)</a>
</li>
<li><a href="reference.html#RNS.Identity.current_ratchet_id">current_ratchet_id() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Identity.CURVE">CURVE (RNS.Identity attribute)</a>
@@ -330,6 +332,8 @@
<h2>E</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.enable_ratchets">enable_ratchets() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.encrypt">encrypt() (RNS.Destination method)</a>
<ul>
@@ -339,6 +343,8 @@
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet.ENCRYPTED_MDU">ENCRYPTED_MDU (RNS.Packet attribute)</a>
</li>
<li><a href="reference.html#RNS.Destination.enforce_ratchets">enforce_ratchets() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">ESTABLISHMENT_TIMEOUT_PER_HOP (RNS.Link attribute)</a>
</li>
@@ -396,10 +402,14 @@
</ul></li>
<li><a href="reference.html#RNS.Identity.get_public_key">get_public_key() (RNS.Identity method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_q">get_q() (RNS.Link method)</a>
<ul>
<li><a href="reference.html#RNS.Packet.get_q">(RNS.Packet method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.get_q">get_q() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
@@ -411,13 +421,21 @@
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_rssi">get_rssi() (RNS.Link method)</a>
<ul>
<li><a href="reference.html#RNS.Packet.get_rssi">(RNS.Packet method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_segments">get_segments() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_snr">get_snr() (RNS.Link method)</a>
<ul>
<li><a href="reference.html#RNS.Packet.get_snr">(RNS.Packet method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
<ul>
@@ -490,14 +508,18 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link">Link (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.LINK_MTU_DISCOVERY">LINK_MTU_DISCOVERY (RNS.Reticulum attribute)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.link_mtu_discovery">link_mtu_discovery() (RNS.Reticulum static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.load_private_key">load_private_key() (RNS.Destination method)</a>
<ul>
<li><a href="reference.html#RNS.Identity.load_private_key">(RNS.Identity method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity.load_public_key">load_public_key() (RNS.Identity method)</a>
</li>
</ul></td>
@@ -508,7 +530,7 @@
<h2>M</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Channel.Channel.MDU">MDU (RNS.Channel.Channel property)</a>
<li><a href="reference.html#RNS.Channel.Channel.mdu">mdu (RNS.Channel.Channel property)</a>
</li>
<li><a href="reference.html#RNS.MessageBase">MessageBase (class in RNS)</a>
</li>
@@ -568,6 +590,14 @@
<h2>R</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.RATCHET_COUNT">RATCHET_COUNT (RNS.Destination attribute)</a>
</li>
<li><a href="reference.html#RNS.Identity.RATCHET_EXPIRY">RATCHET_EXPIRY (RNS.Identity attribute)</a>
</li>
<li><a href="reference.html#RNS.Destination.RATCHET_INTERVAL">RATCHET_INTERVAL (RNS.Destination attribute)</a>
</li>
<li><a href="reference.html#RNS.Identity.RATCHETSIZE">RATCHETSIZE (RNS.Identity attribute)</a>
</li>
<li><a href="reference.html#RNS.RawChannelReader">RawChannelReader (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.RawChannelWriter">RawChannelWriter (class in RNS)</a>
@@ -580,12 +610,12 @@
</li>
<li><a href="reference.html#RNS.Channel.Channel.register_message_type">register_message_type() (RNS.Channel.Channel method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.remote_management_enabled">remote_management_enabled() (RNS.Reticulum static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Channel.Channel.remove_message_handler">remove_message_handler() (RNS.Channel.Channel method)</a>
</li>
<li><a href="reference.html#RNS.RawChannelReader.remove_ready_callback">remove_ready_callback() (RNS.RawChannelReader method)</a>
@@ -633,6 +663,8 @@
<li><a href="reference.html#RNS.Destination.set_proof_requested_callback">set_proof_requested_callback() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_proof_strategy">set_proof_strategy() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_ratchet_interval">set_ratchet_interval() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_remote_identified_callback">set_remote_identified_callback() (RNS.Link method)</a>
</li>
@@ -645,6 +677,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>
+213 -38
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Getting Started Fast - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Getting Started Fast - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -241,9 +241,12 @@ and install them offline using <code class="docutils literal notranslate"><span
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.5.1</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
</pre></div>
</div>
</section>
<p>For more detailed installation instructions, please see the
<a class="reference internal" href="#install-guides"><span class="std std-ref">Platform-Specific Install Notes</span></a> section.</p>
<p>After installation is complete, it might be helpful to refer to the
<a class="reference internal" href="using.html#using-main"><span class="std std-ref">Using Reticulum on Your System</span></a> chapter.</p>
<section id="resolving-dependency-installation-issues">
<h2>Resolving Dependency &amp; Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h2>
<h3>Resolving Dependency &amp; Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h3>
<p>On some platforms, there may not be binary packages available for all dependencies, and
<code class="docutils literal notranslate"><span class="pre">pip</span></code> installation may fail with an error message. In these cases, the issue can usually
be resolved by installing the development essentials packages for your platform:</p>
@@ -261,6 +264,7 @@ be resolved by installing the development essentials packages for your platform:
dependencies from source, and complete installation even on platforms that dont have pre-
compiled packages available.</p>
</section>
</section>
<section id="try-using-a-reticulum-based-program">
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this heading">#</a></h2>
<p>If you simply want to try using a program built with Reticulum, a few different
@@ -288,7 +292,8 @@ provides a complete encrypted communications suite built with Reticulum. It feat
encrypted messaging (both direct and delayed-delivery for offline users), file sharing,
and has a built-in text-browser and page server with support for dynamically rendered pages,
user authentication and more.</p>
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" /></a>
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" />
</a>
<p><a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> is a user-facing client
for the messaging and information-sharing protocol
<a class="reference external" href="https://github.com/markqvist/lxmf">LXMF</a>, another project built with Reticulum.</p>
@@ -300,17 +305,22 @@ for the messaging and information-sharing protocol
<span class="n">nomadnet</span>
</pre></div>
</div>
<p><strong>Please Note</strong>: If this is the very first time you use pip to install a program
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>If this is the very first time you use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a “command not found” error or similar when running the
program, reboot your system and try again.</p>
program, reboot your system and try again. In some cases, you may even need to
manually add the <code class="docutils literal notranslate"><span class="pre">pip</span></code> install path to your <code class="docutils literal notranslate"><span class="pre">PATH</span></code> environment variable.</p>
</div>
</section>
<section id="sideband">
<h3>Sideband<a class="headerlink" href="#sideband" title="Permalink to this heading">#</a></h3>
<p>If you would rather use a program with a graphical user interface, you can take
a look at <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>, which is available for Android,
Linux, macOS and Windows.</p>
<a class="reference external image-reference" href="_images/sideband_devices.webp"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" /></a>
<a class="reference external image-reference" href="_images/sideband_devices.webp"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" />
</a>
<p>Sideband allows you to communicate with other people or LXMF-compatible
systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR
Paper Messages, or anything else Reticulum supports. It also interoperates with
@@ -321,7 +331,8 @@ the Nomad Network program.</p>
<p>The <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> application
is a user-friendly LXMF client for macOS and Windows, that also includes voice call
functionality, and a range of other interesting functions.</p>
<a class="reference external image-reference" href="_images/meshchat_1.webp"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" /></a>
<a class="reference external image-reference" href="_images/meshchat_1.webp"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" />
</a>
<p>Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.</p>
</section>
@@ -456,6 +467,16 @@ refer to these additional external resources:</p>
you are welcome to head over to the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">GitHub discussion pages</a>
and propose adding an interface for the hardware.</p>
</section>
<section id="creating-and-using-custom-interfaces">
<h2>Creating and Using Custom Interfaces<a class="headerlink" href="#creating-and-using-custom-interfaces" title="Permalink to this heading">#</a></h2>
<p>While Reticulum includes a flexible and broad range of built-in interfaces, these
will not cover every conceivable type of communications hardware that Reticulum
can potentially use to communicate.</p>
<p>It is therefore possible to easily write your own interface modules, that can be
loaded at run-time and used on-par with any of the built-in interface types.</p>
<p>For more information on this subject, and code examples to build on, please see
the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Configuring Interfaces</span></a> chapter.</p>
</section>
<section id="develop-a-program-with-reticulum">
<h2>Develop a Program with Reticulum<a class="headerlink" href="#develop-a-program-with-reticulum" title="Permalink to this heading">#</a></h2>
<p>If you want to develop programs that use Reticulum, the easiest way to get
@@ -466,11 +487,8 @@ started is to install the latest release of Reticulum via pip:</p>
<p>The above command will install Reticulum and dependencies, and you will be
ready to import and use RNS in your own programs. The next step will most
likely be to look at some <a class="reference internal" href="examples.html#examples-main"><span class="std std-ref">Example Programs</span></a>.</p>
<p>For extended functionality, you can install optional dependencies:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">pyserial</span>
</pre></div>
</div>
<p>Further information can be found in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>.</p>
<p>The entire Reticulum API is documented in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>
chapter of this manual.</p>
</section>
<section id="participate-in-reticulum-development">
<h2>Participate in Reticulum Development<a class="headerlink" href="#participate-in-reticulum-development" title="Permalink to this heading">#</a></h2>
@@ -516,7 +534,7 @@ the <a class="reference external" href="https://github.com/markqvist/Reticulum/d
or ask one of the developers or maintainers for a good place to start.</p>
</section>
<section id="platform-specific-install-notes">
<h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
<span id="install-guides"></span><h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
<p>Some platforms require a slightly different installation procedure, or have
various quirks that are worth being aware of. These are listed here.</p>
<section id="android">
@@ -581,7 +599,7 @@ here at a later point. Until then you can use the <a class="reference external"
<section id="arm64">
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
<p>On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> (or similar) before
installing Reticulum or programs that depend on Reticulum.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
@@ -591,15 +609,8 @@ installing Reticulum or programs that depend on Reticulum.</p>
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
</section>
<section id="raspberry-pi">
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
dont always have packages available for some dependencies.</p>
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing some packages, and is not
detailed in this manual.</p>
<p>With these packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> will be able to build any missing dependencies
on your system locally.</p>
</section>
<section id="debian-bookworm">
<h3>Debian Bookworm<a class="headerlink" href="#debian-bookworm" title="Permalink to this heading">#</a></h3>
@@ -625,10 +636,137 @@ following section:</p>
break-system-packages = true
</pre></div>
</div>
<p>Please note that the “break-system-packages” directive is a somewhat misleading choice
<p>For a one-shot installation of Reticulum, without globally enabling the <code class="docutils literal notranslate"><span class="pre">break-system-packages</span></code>
option, you can use the following command:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>pip install rns --break-system-packages
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
cases lead to version conflicts, it does not generally pose any problems.</p>
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.</p>
</div>
</section>
<section id="macos">
<h3>MacOS<a class="headerlink" href="#macos" title="Permalink to this heading">#</a></h3>
<p>To install Reticulum on macOS, you will need to have Python and the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package
manager installed.</p>
<p>Systems running macOS can vary quite widely in whether or not Python is pre-installed,
and if it is, which version is installed, and whether the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager is
also installed and set up. If in doubt, you can <a class="reference external" href="https://www.python.org/downloads/">download and install</a>
Python manually.</p>
<p>When Python and <code class="docutils literal notranslate"><span class="pre">pip</span></code> is available on your system, simply open a terminal window
and use one of the following commands:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Reticulum and utilities with pip:</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
<span class="c1"># On some versions, you may need to use the</span>
<span class="c1"># flag --break-system-packages to install:</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span> <span class="o">--</span><span class="k">break</span><span class="o">-</span><span class="n">system</span><span class="o">-</span><span class="n">packages</span>
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.</p>
</div>
<p>Additionally, some version combinations of macOS and Python require you to
manually add your installed <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages directory to your <cite>PATH</cite> environment
variable, before you can use installed commands in your terminal. Usually, adding
the following line to your shell init script (for example <code class="docutils literal notranslate"><span class="pre">~/.zshrc</span></code>) will be enough:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>export PATH=$PATH:~/Library/Python/3.9/bin
</pre></div>
</div>
<p>Adjust Python version and shell init script location according to your system.</p>
</section>
<section id="openwrt">
<h3>OpenWRT<a class="headerlink" href="#openwrt" title="Permalink to this heading">#</a></h3>
<p>On OpenWRT systems with sufficient storage and memory, you can install
Reticulum and related utilities using the <cite>opkg</cite> package manager and <cite>pip</cite>.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>At the time of releasing this manual, work is underway to create pre-built
Reticulum packages for OpenWRT, with full configuration, service
and <code class="docutils literal notranslate"><span class="pre">uci</span></code> integration. Please see the <a class="reference external" href="https://github.com/gretel/feed-reticulum">feed-reticulum</a>
and <a class="reference external" href="https://github.com/gretel/reticulum-openwrt">reticulum-openwrt</a>
repositories for more information.</p>
</div>
<p>To install Reticulum on OpenWRT, first log into a command line session, and
then use the following instructions:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
<span class="n">opkg</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">cryptography</span> <span class="n">python3</span><span class="o">-</span><span class="n">pyserial</span>
<span class="c1"># Install Reticulum</span>
<span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
<span class="c1"># Start rnsd with debug logging enabled</span>
<span class="n">rnsd</span> <span class="o">-</span><span class="n">vvv</span>
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The above instructions have been verified and tested on OpenWRT 21.02 only.
It is likely that other versions may require slightly altered installation
commands or package names. You will also need enough free space in your
overlay FS, and enough free RAM to actually run Reticulum and any related
programs and utilities.</p>
</div>
<p>Depending on your device configuration, you may need to adjust firewall rules
for Reticulum connectivity to and from your device to work. Until proper
packaging is ready, you will also need to manually create a service or startup
script to automatically laucnh Reticulum at boot time.</p>
<p>Please also note that the <cite>AutoInterface</cite> requires link-local IPv6 addresses
to be enabled for any Ethernet and WiFi devices you intend to use. If <code class="docutils literal notranslate"><span class="pre">ip</span> <span class="pre">a</span></code>
shows an address starting with <code class="docutils literal notranslate"><span class="pre">fe80::</span></code> for the device in question,
<code class="docutils literal notranslate"><span class="pre">AutoInterface</span></code> should work for that device.</p>
</section>
<section id="raspberry-pi">
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
dont always have packages available for some dependencies. If Python and the
<cite>pip</cite> package manager is not already installed, do that first, and then
install Reticulum using <cite>pip</cite>.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">cryptography</span> <span class="n">python3</span><span class="o">-</span><span class="n">pyserial</span>
<span class="c1"># Install Reticulum</span>
<span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span> <span class="o">--</span><span class="k">break</span><span class="o">-</span><span class="n">system</span><span class="o">-</span><span class="n">packages</span>
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.</p>
</div>
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing required build dependencies,
and is not detailed in this manual.</p>
</section>
<section id="risc-v">
<h3>RISC-V<a class="headerlink" href="#risc-v" title="Permalink to this heading">#</a></h3>
<p>On some architectures, including RISC-V, not all dependencies have precompiled
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> (or similar) before
installing Reticulum or programs that depend on Reticulum.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
<span class="c1"># Install Reticulum</span>
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
<p>With these packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> will be able to build any missing dependencies
on your system locally.</p>
</section>
<section id="ubuntu-lunar">
<h3>Ubuntu Lunar<a class="headerlink" href="#ubuntu-lunar" title="Permalink to this heading">#</a></h3>
@@ -654,14 +792,48 @@ following section:</p>
break-system-packages = true
</pre></div>
</div>
<p>Please note that the “break-system-packages” directive is a somewhat misleading choice
<p>For a one-shot installation of Reticulum, without globally enabling the <code class="docutils literal notranslate"><span class="pre">break-system-packages</span></code>
option, you can use the following command:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>pip install rns --break-system-packages
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this _could_ in rare
cases lead to version conflicts, it does not generally pose any problems.</p>
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.</p>
</div>
</section>
<section id="windows">
<h3>Windows<a class="headerlink" href="#windows" title="Permalink to this heading">#</a></h3>
<p>On Windows operating systems, the easiest way to install Reticulum is by using the
<code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager from the command line (either the command prompt or Windows
Powershell).</p>
<p>If you dont already have Python installed, <a class="reference external" href="https://www.python.org/downloads/">download and install Python</a>.
At the time of publication of this manual, the recommended version is <a class="reference external" href="https://www.python.org/downloads/release/python-3127">Python 3.12.7</a>.</p>
<p><strong>Important!</strong> When asked by the installer, make sure to add the Python program to
your PATH environment variables. If you dont do this, you will not be able to
use the <code class="docutils literal notranslate"><span class="pre">pip</span></code> installer, or run the included Reticulum utility programs (such as
<code class="docutils literal notranslate"><span class="pre">rnsd</span></code> and <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code>) from the command line.</p>
<p>After installing Python, open the command prompt or Windows Powershell, and type:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
<p>You can now use Reticulum and all included utility programs directly from your
preferred command line interface.</p>
</section>
</section>
<section id="pure-python-reticulum">
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this heading">#</a></h2>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>If you use the <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> package to run Reticulum on systems that
do not support <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>, it is
important that you read and understand the <a class="reference internal" href="understanding.html#understanding-primitives"><span class="std std-ref">Cryptographic Primitives</span></a>
section of this manual.</p>
</div>
<p>In some rare cases, and on more obscure system types, it is not possible to
install one or more dependencies. In such situations,
you can use the <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> package instead of the <code class="docutils literal notranslate"><span class="pre">rns</span></code> package, or use <code class="docutils literal notranslate"><span class="pre">pip</span></code>
@@ -675,10 +847,6 @@ only if they are <em>needed</em> and <em>available</em>. If for example you want
on a system that cannot support <code class="docutils literal notranslate"><span class="pre">pyserial</span></code>, it is perfectly possible to do so using
the <cite>rnspure</cite> package, but Reticulum will not be able to use serial-based interfaces.
All other available modules will still be loaded when needed.</p>
<p><strong>Please Note!</strong> If you use the <cite>rnspure</cite> package to run Reticulum on systems that
do not support <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>, it is
important that you read and understand the <a class="reference internal" href="understanding.html#understanding-primitives"><span class="std std-ref">Cryptographic Primitives</span></a>
section of this manual.</p>
</section>
</section>
@@ -739,8 +907,10 @@ section of this manual.</p>
<div class="toc-tree">
<ul>
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a><ul>
<li><a class="reference internal" href="#resolving-dependency-installation-issues">Resolving Dependency &amp; Installation Issues</a></li>
</ul>
</li>
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
@@ -753,14 +923,19 @@ section of this manual.</p>
<li><a class="reference internal" href="#connecting-reticulum-instances-over-the-internet">Connecting Reticulum Instances Over the Internet</a></li>
<li><a class="reference internal" href="#connect-to-the-public-testnet">Connect to the Public Testnet</a></li>
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
<li><a class="reference internal" href="#creating-and-using-custom-interfaces">Creating and Using Custom Interfaces</a></li>
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
<li><a class="reference internal" href="#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
<li><a class="reference internal" href="#android">Android</a></li>
<li><a class="reference internal" href="#arm64">ARM64</a></li>
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
<li><a class="reference internal" href="#debian-bookworm">Debian Bookworm</a></li>
<li><a class="reference internal" href="#macos">MacOS</a></li>
<li><a class="reference internal" href="#openwrt">OpenWRT</a></li>
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
<li><a class="reference internal" href="#risc-v">RISC-V</a></li>
<li><a class="reference internal" href="#ubuntu-lunar">Ubuntu Lunar</a></li>
<li><a class="reference internal" href="#windows">Windows</a></li>
</ul>
</li>
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
+126 -47
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Configuring Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Communications Hardware - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Communications Hardware - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -279,77 +279,150 @@ completely from scratch, to your exact desired specifications, this chapter
will explain the easiest possible approach to creating RNodes: Using common
LoRa development boards. This approach can be boiled down to two simple steps:</p>
<ol class="arabic simple">
<li><p>Obtain one or more supported development boards</p></li>
<li><p>Install the RNode firmware with the automated installer</p></li>
<li><p>Obtain one or more <a class="reference internal" href="#rnode-supported"><span class="std std-ref">supported development boards</span></a></p></li>
<li><p>Install the RNode firmware with the <a class="reference internal" href="#rnode-installation"><span class="std std-ref">automated installer</span></a></p></li>
</ol>
<p>Once the firmware has been installed and provisioned by the install script, it
is ready to use with any software that supports RNodes, including Reticulum.
The device can be used with Reticulum by adding an <a class="reference internal" href="interfaces.html#interfaces-rnode"><span class="std std-ref">RNodeInterface</span></a>
to the configuration.</p>
</section>
<section id="supported-boards">
<span id="rnode-supported"></span><h3>Supported Boards<a class="headerlink" href="#supported-boards" title="Permalink to this heading">#</a></h3>
<section id="supported-boards-and-devices">
<span id="rnode-supported"></span><h3>Supported Boards and Devices<a class="headerlink" href="#supported-boards-and-devices" title="Permalink to this heading">#</a></h3>
<p>To create one or more RNodes, you will need to obtain supported development
boards. The following boards are supported by the auto-installer.</p>
<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 +448,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">
@@ -391,8 +462,10 @@ Most devices will behave like this by default, or allow it via configuration opt
and start communicating over them using Reticulum. It is not necessary to enable any IP
infrastructure such as DHCP servers, DNS or similar, as long as at least Ethernet is
available, and packets are passed transparently over the physical WiFi-based devices.</p>
<a class="reference internal image-reference" href="_images/radio_rblhg5.png"><img alt="_images/radio_rblhg5.png" src="_images/radio_rblhg5.png" style="width: 49%;" /></a>
<a class="reference internal image-reference" href="_images/radio_is5ac.png"><img alt="_images/radio_is5ac.png" src="_images/radio_is5ac.png" style="width: 49%;" /></a>
<a class="reference internal image-reference" href="_images/radio_rblhg5.png"><img alt="_images/radio_rblhg5.png" src="_images/radio_rblhg5.png" style="width: 49%;" />
</a>
<a class="reference internal image-reference" href="_images/radio_is5ac.png"><img alt="_images/radio_is5ac.png" src="_images/radio_is5ac.png" style="width: 49%;" />
</a>
<p>Below is a list of example WiFi (and similar) radios that work well for high capacity
Reticulum links over long distances:</p>
<ul class="simple">
@@ -491,12 +564,18 @@ can be used with Reticulum. This includes virtual software modems such as
<li><a class="reference internal" href="#combining-hardware-types">Combining Hardware Types</a></li>
<li><a class="reference internal" href="#rnode">RNode</a><ul>
<li><a class="reference internal" href="#creating-rnodes">Creating RNodes</a></li>
<li><a class="reference internal" href="#supported-boards">Supported Boards</a><ul>
<li><a class="reference internal" href="#supported-boards-and-devices">Supported Boards and Devices</a><ul>
<li><a class="reference internal" href="#lilygo-t-beam-supreme">LilyGO T-Beam Supreme</a></li>
<li><a class="reference internal" href="#lilygo-t-beam">LilyGO T-Beam</a></li>
<li><a class="reference internal" href="#lilygo-t3s3">LilyGO T3S3</a></li>
<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>
+18 -8
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="#"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -241,8 +241,10 @@ to participate in the development of Reticulum itself.</p>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency &amp; Installation Issues</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a><ul>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency &amp; Installation Issues</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
@@ -255,14 +257,19 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#connecting-reticulum-instances-over-the-internet">Connecting Reticulum Instances Over the Internet</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#connect-to-the-public-testnet">Connect to the Public Testnet</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#creating-and-using-custom-interfaces">Creating and Using Custom Interfaces</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#android">Android</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#arm64">ARM64</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#debian-bookworm">Debian Bookworm</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#macos">MacOS</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#openwrt">OpenWRT</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#risc-v">RISC-V</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#ubuntu-lunar">Ubuntu Lunar</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#windows">Windows</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
@@ -281,6 +288,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>
@@ -320,7 +328,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="hardware.html#combining-hardware-types">Combining Hardware Types</a></li>
<li class="toctree-l2"><a class="reference internal" href="hardware.html#rnode">RNode</a><ul>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#creating-rnodes">Creating RNodes</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#supported-boards">Supported Boards</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#supported-boards-and-devices">Supported Boards and Devices</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#installation">Installation</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#usage-with-reticulum">Usage with Reticulum</a></li>
</ul>
@@ -332,6 +340,7 @@ to participate in the development of Reticulum itself.</p>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#custom-interfaces">Custom Interfaces</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#auto-interface">Auto Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#i2p-interface">I2P Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
@@ -370,6 +379,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="examples.html#channel">Channel</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#buffer">Buffer</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#custom-interfaces">Custom Interfaces</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a><ul>
+170 -76
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Configuring Interfaces - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Configuring Interfaces - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -233,6 +233,13 @@ and gives example configurations for the respective interface types.</p>
<p>For a high-level overview of how networks can be formed over different interface
types, have a look at the <a class="reference internal" href="networks.html#networks-main"><span class="std std-ref">Building Networks</span></a> chapter of this
manual.</p>
<section id="custom-interfaces">
<span id="interfaces-custom"></span><h2>Custom Interfaces<a class="headerlink" href="#custom-interfaces" title="Permalink to this heading">#</a></h2>
<p>In addition to the built-in interface types, Reticulum is <strong>fully extensible</strong> with
custom, user- or community-supplied interfaces, and creating custom interface
modules is straightforward. Please see the <a class="reference internal" href="examples.html#example-custominterface"><span class="std std-ref">custom interface</span></a>
example for basic interface code to build upon.</p>
</section>
<section id="auto-interface">
<span id="interfaces-auto"></span><h2>Auto Interface<a class="headerlink" href="#auto-interface" title="Permalink to this heading">#</a></h2>
<p>The Auto Interface enables communication with other discoverable Reticulum
@@ -346,11 +353,14 @@ list of I2P base32 addresses to the <code class="docutils literal notranslate"><
<p>It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.</p>
<p><strong>Please Note!</strong> While the I2P interface is the simplest way to use
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.</p>
</div>
<p>It is important to note that the two methods are <em>interchangably compatible</em>.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
@@ -360,7 +370,7 @@ use-cases.</p>
<section id="tcp-server-interface">
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this heading">#</a></h2>
<p>The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
the Internet or private IPv4 and IPv6 networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
@@ -387,8 +397,35 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
<span class="c1"># port = 4242</span>
</pre></div>
</div>
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
<p>If you are using the interface on a device which has both IPv4 and IPv6 addresses available,
you can use the <code class="docutils literal notranslate"><span class="pre">prefer_ipv6</span></code> option to bind to the IPv6 address:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
<span class="c1"># specified IP address and port number.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">device</span> <span class="o">=</span> <span class="n">eth0</span>
<span class="n">port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="n">prefer_ipv6</span> <span class="o">=</span> <span class="kc">True</span>
</pre></div>
</div>
<p>To use the TCP Server Interface over <a class="reference external" href="https://yggdrasil-network.github.io/">Yggdrasil</a>, you
can simply specify the Yggdrasil <code class="docutils literal notranslate"><span class="pre">tun</span></code> device and a listening port, like so:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Yggdrasil</span> <span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">device</span> <span class="o">=</span> <span class="n">tun0</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4343</span>
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">on</span> <span class="n">I2P</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
@@ -409,7 +446,7 @@ same TCP Server interface at the same time.</p>
This means that Reticulum will gracefully handle IP links that go up and down,
and restore connectivity after a failure, once the other end of a TCP interface reappears.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of a TCP Client interface. The</span>
<span class="c1"># target_host can either be an IP address or a hostname.</span>
<span class="c1"># target_host can be a hostname or an IPv4 or IPv6 address.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
@@ -418,6 +455,15 @@ and restore connectivity after a failure, once the other end of a TCP interface
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
</pre></div>
</div>
<p>To use the TCP Client Interface over <a class="reference external" href="https://yggdrasil-network.github.io/">Yggdrasil</a>, simply
specify the target Yggdrasil IPv6 address and port, like so:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Yggdrasil</span> <span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="mi">201</span><span class="p">:</span><span class="mi">5</span><span class="n">d78</span><span class="p">:</span><span class="n">af73</span><span class="p">:</span><span class="mi">5</span><span class="n">caf</span><span class="p">:</span><span class="n">a4de</span><span class="p">:</span><span class="n">a79f</span><span class="p">:</span><span class="mi">3278</span><span class="p">:</span><span class="mf">71e5</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4343</span>
</pre></div>
</div>
<p>It is also possible to use this interface type to connect via other programs
or hardware devices that expose a KISS interface on a TCP port, for example
software-based soundmodems. To do this, use the <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code> option:</p>
@@ -438,8 +484,11 @@ and programs like soundmodems and similar over TCP. When using the
never enable <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code>, since this will disable internal reliability and
recovery mechanisms that greatly improves performance over unreliable and
intermittent TCP links.</p>
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">over</span> <span class="n">I2P</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
@@ -455,11 +504,14 @@ you must use the i2p_tunneled option:</p>
private and the internet. It can also allow broadcast communication
over IP networks, so it can provide an easy way to enable connectivity
with all other peers on a local area network.</p>
<p><em>Please Note!</em> Using broadcast UDP traffic has performance implications,
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Using broadcast UDP traffic has performance implications,
especially on WiFi. If your goal is simply to enable easy communication
with all peers in your local Ethernet broadcast domain, the
<a class="reference internal" href="#interfaces-auto"><span class="std std-ref">Auto Interface</span></a> performs better, and is even
easier to use.</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example enables communication with other</span>
<span class="c1"># local Reticulum peers over UDP.</span>
@@ -508,6 +560,12 @@ easier to use.</p>
<span id="interfaces-rnode"></span><h2>RNode LoRa Interface<a class="headerlink" href="#rnode-lora-interface" title="Permalink to this heading">#</a></h2>
<p>To use Reticulum over LoRa, the <a class="reference external" href="https://unsigned.io/rnode/">RNode</a> interface
can be used, and offers full control over LoRa parameters.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of how to add a LoRa interface</span>
<span class="c1"># using the RNode LoRa transceiver.</span>
@@ -520,6 +578,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>
@@ -573,6 +648,12 @@ can be used, and offers full control over LoRa parameters.</p>
<span id="interfaces-rnode-multi"></span><h2>RNode Multi Interface<a class="headerlink" href="#rnode-multi-interface" title="Permalink to this heading">#</a></h2>
<p>For RNodes that support multiple LoRa transceivers, the RNode
Multi interface can be used to configure sub-interfaces individually.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of how to add an RNode Multi interface</span>
<span class="c1"># using the RNode LoRa transceiver.</span>
@@ -594,89 +675,89 @@ Multi interface can be used to configure sub-interfaces individually.</p>
<span class="c1"># id_interval = 600</span>
<span class="c1"># A subinterface</span>
<span class="p">[[[</span><span class="n">HIGHDATARATE</span><span class="p">]]]</span>
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="p">[[[</span><span class="n">High</span> <span class="n">Datarate</span><span class="p">]]]</span>
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Set frequency to 2.4GHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">2400000000</span>
<span class="c1"># Set frequency to 2.4GHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">2400000000</span>
<span class="c1"># Set LoRa bandwidth to 1625 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">1625000</span>
<span class="c1"># Set LoRa bandwidth to 1625 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">1625000</span>
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># The virtual port, only the manufacturer</span>
<span class="c1"># or the person who wrote the board config</span>
<span class="c1"># can tell you what it will be for which</span>
<span class="c1"># physical hardware interface</span>
<span class="n">vport</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># The virtual port, only the manufacturer</span>
<span class="c1"># or the person who wrote the board config</span>
<span class="c1"># can tell you what it will be for which</span>
<span class="c1"># physical hardware interface</span>
<span class="n">vport</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Select spreading factor 5. Valid</span>
<span class="c1"># range is 5 through 12, with 5</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># Select spreading factor 5. Valid</span>
<span class="c1"># range is 5 through 12, with 5</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># It is possible to limit the airtime</span>
<span class="c1"># utilisation of an RNode by using the</span>
<span class="c1"># following two configuration options.</span>
<span class="c1"># The short-term limit is applied in a</span>
<span class="c1"># window of approximately 15 seconds,</span>
<span class="c1"># and the long-term limit is enforced</span>
<span class="c1"># over a rolling 60 minute window. Both</span>
<span class="c1"># options are specified in percent.</span>
<span class="c1"># It is possible to limit the airtime</span>
<span class="c1"># utilisation of an RNode by using the</span>
<span class="c1"># following two configuration options.</span>
<span class="c1"># The short-term limit is applied in a</span>
<span class="c1"># window of approximately 15 seconds,</span>
<span class="c1"># and the long-term limit is enforced</span>
<span class="c1"># over a rolling 60 minute window. Both</span>
<span class="c1"># options are specified in percent.</span>
<span class="c1"># airtime_limit_long = 100</span>
<span class="c1"># airtime_limit_short = 100</span>
<span class="c1"># airtime_limit_long = 100</span>
<span class="c1"># airtime_limit_short = 100</span>
<span class="p">[[[</span><span class="n">LOWDATARATE</span><span class="p">]]]</span>
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="p">[[[</span><span class="n">Low</span> <span class="n">Datarate</span><span class="p">]]]</span>
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Set frequency to 865.6 MHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">865600000</span>
<span class="c1"># Set frequency to 865.6 MHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">865600000</span>
<span class="c1"># The virtual port, only the manufacturer</span>
<span class="c1"># or the person who wrote the board config</span>
<span class="c1"># can tell you what it will be for which</span>
<span class="c1"># physical hardware interface</span>
<span class="n">vport</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># The virtual port, only the manufacturer</span>
<span class="c1"># or the person who wrote the board config</span>
<span class="c1"># can tell you what it will be for which</span>
<span class="c1"># physical hardware interface</span>
<span class="n">vport</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Select spreading factor 7. Valid</span>
<span class="c1"># range is 5 through 12, with 5</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">7</span>
<span class="c1"># Select spreading factor 7. Valid</span>
<span class="c1"># range is 5 through 12, with 5</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">7</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># It is possible to limit the airtime</span>
<span class="c1"># utilisation of an RNode by using the</span>
<span class="c1"># following two configuration options.</span>
<span class="c1"># The short-term limit is applied in a</span>
<span class="c1"># window of approximately 15 seconds,</span>
<span class="c1"># and the long-term limit is enforced</span>
<span class="c1"># over a rolling 60 minute window. Both</span>
<span class="c1"># options are specified in percent.</span>
<span class="c1"># It is possible to limit the airtime</span>
<span class="c1"># utilisation of an RNode by using the</span>
<span class="c1"># following two configuration options.</span>
<span class="c1"># The short-term limit is applied in a</span>
<span class="c1"># window of approximately 15 seconds,</span>
<span class="c1"># and the long-term limit is enforced</span>
<span class="c1"># over a rolling 60 minute window. Both</span>
<span class="c1"># options are specified in percent.</span>
<span class="c1"># airtime_limit_long = 100</span>
<span class="c1"># airtime_limit_short = 100</span>
<span class="c1"># airtime_limit_long = 100</span>
<span class="c1"># airtime_limit_short = 100</span>
</pre></div>
</div>
</section>
@@ -727,6 +808,12 @@ Reticulum will try to respawn the program after waiting for <code class="docutil
radio modems and TNCs, including <a class="reference external" href="https://unsigned.io/openmodem/">OpenModem</a>.
KISS interfaces can also be configured to periodically send out beacons
for station identification purposes.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">KISSInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
@@ -784,6 +871,12 @@ layer for anything, and it incurs extra overhead on every packet to
encapsulate in AX.25.</p>
<p>A more efficient way is to use the plain KISS interface with the
beaconing functionality described above.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Radio frequency spectrum is a legally controlled resource, and legislation
varies widely around the world. It is your responsibility to be aware of any
relevant regulation for your location, and to make decisions accordingly.</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">AX</span><span class="mf">.25</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">AX25KISSInterface</span>
@@ -1209,6 +1302,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
<div class="toc-tree">
<ul>
<li><a class="reference internal" href="#">Configuring Interfaces</a><ul>
<li><a class="reference internal" href="#custom-interfaces">Custom Interfaces</a></li>
<li><a class="reference internal" href="#auto-interface">Auto Interface</a></li>
<li><a class="reference internal" href="#i2p-interface">I2P Interface</a></li>
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
+4 -4
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Configuring Interfaces" href="interfaces.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Building Networks - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Building Networks - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
Binary file not shown.
+187 -16
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>API Reference - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>API Reference - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -228,7 +228,7 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
<p id="api-reticulum"><h3> Reticulum </h3></p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Reticulum">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">require_shared_instance</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
<dd><p>This class is used to initialise access to Reticulum within a
program. You must create exactly one instance of this class before
carrying out any other RNS operations, such as creating destinations
@@ -261,6 +261,17 @@ MTU is a prerequisite for peers to communicate in the same network.</p>
the default value.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Reticulum.LINK_MTU_DISCOVERY">
<span class="sig-name descname"><span class="pre">LINK_MTU_DISCOVERY</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">False</span></em><a class="headerlink" href="#RNS.Reticulum.LINK_MTU_DISCOVERY" title="Permalink to this definition">#</a></dt>
<dd><p>Whether automatic link MTU discovery is enabled by default in this
release. Link MTU discovery significantly increases throughput over
fast links, but requires all intermediary hops to also support it.
Support for this feature was added in RNS version 0.9.0. This option
will become enabled by default in the near future. Please update your
RNS instances.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Reticulum.ANNOUNCE_CAP">
<span class="sig-name descname"><span class="pre">ANNOUNCE_CAP</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2</span></em><a class="headerlink" href="#RNS.Reticulum.ANNOUNCE_CAP" title="Permalink to this definition">#</a></dt>
@@ -319,6 +330,21 @@ and pass announces over the network.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Reticulum.link_mtu_discovery">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">link_mtu_discovery</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.link_mtu_discovery" title="Permalink to this definition">#</a></dt>
<dd><p>Returns whether link MTU discovery is enabled for the running
instance.</p>
<p>When link MTU discovery is enabled, Reticulum will
automatically upgrade link MTUs to the highest supported
value, increasing transfer speed and efficiency.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>True if link MTU discovery is enabled, False if not.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Reticulum.remote_management_enabled">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">remote_management_enabled</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.remote_management_enabled" title="Permalink to this definition">#</a></dt>
@@ -356,7 +382,21 @@ for all encrypted communication over Reticulum networks.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.KEYSIZE">
<span class="sig-name descname"><span class="pre">KEYSIZE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">512</span></em><a class="headerlink" href="#RNS.Identity.KEYSIZE" title="Permalink to this definition">#</a></dt>
<dd><p>X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
<dd><p>X.25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.RATCHETSIZE">
<span class="sig-name descname"><span class="pre">RATCHETSIZE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">256</span></em><a class="headerlink" href="#RNS.Identity.RATCHETSIZE" title="Permalink to this definition">#</a></dt>
<dd><p>X.25519 ratchet key size in bits.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.RATCHET_EXPIRY">
<span class="sig-name descname"><span class="pre">RATCHET_EXPIRY</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2592000</span></em><a class="headerlink" href="#RNS.Identity.RATCHET_EXPIRY" title="Permalink to this definition">#</a></dt>
<dd><p>The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
announced ratchet, and remember it for up to <code class="docutils literal notranslate"><span class="pre">RATCHET_EXPIRY</span></code> since receiving it, after which it will be discarded.
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.</p>
</dd></dl>
<dl class="py attribute">
@@ -403,7 +443,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
<dd class="field-odd"><p><strong>data</strong> Data to be hashed as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>SHA-256 hash as <em>bytes</em></p>
<dd class="field-even"><p>SHA-256 hash as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -417,7 +457,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
<dd class="field-odd"><p><strong>data</strong> Data to be hashed as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>Truncated SHA-256 hash as <em>bytes</em></p>
<dd class="field-even"><p>Truncated SHA-256 hash as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -431,7 +471,21 @@ for addressable hashes and other purposes. Non-configurable.</p>
<dd class="field-odd"><p><strong>data</strong> Data to be hashed as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>Truncated SHA-256 hash of random data as <em>bytes</em></p>
<dd class="field-even"><p>Truncated SHA-256 hash of random data as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.current_ratchet_id">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">current_ratchet_id</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.current_ratchet_id" title="Permalink to this definition">#</a></dt>
<dd><p>Get the ID of the currently used ratchet key for a given destination hash</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><p><strong>destination_hash</strong> A destination hash as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns<span class="colon">:</span></dt>
<dd class="field-even"><p>A ratchet ID as <em>bytes</em> or <em>None</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -532,7 +586,7 @@ communication for the identity. Be very careful with this method.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
<dd><p>Encrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -549,7 +603,7 @@ communication for the identity. Be very careful with this method.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.decrypt">
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchets</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">enforce_ratchets</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet_id_receiver</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
<dd><p>Decrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -611,7 +665,7 @@ communication for the identity. Be very careful with this method.</p>
instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its
presence on the network, which will also distribute necessary keys for
presence on the network, which will distribute necessary keys for
encrypted communication with it.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -624,6 +678,18 @@ encrypted communication with it.</p>
</ul>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Destination.RATCHET_COUNT">
<span class="sig-name descname"><span class="pre">RATCHET_COUNT</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">512</span></em><a class="headerlink" href="#RNS.Destination.RATCHET_COUNT" title="Permalink to this definition">#</a></dt>
<dd><p>The default number of generated ratchet keys a destination will retain, if it has ratchets enabled.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Destination.RATCHET_INTERVAL">
<span class="sig-name descname"><span class="pre">RATCHET_INTERVAL</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">1800</span></em><a class="headerlink" href="#RNS.Destination.RATCHET_INTERVAL" title="Permalink to this definition">#</a></dt>
<dd><p>The minimum interval between rotating ratchet keys, in seconds.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.expand_name">
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">expand_name</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">app_name</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span><span class="n"><span class="pre">aspects</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.expand_name" title="Permalink to this definition">#</a></dt>
@@ -774,6 +840,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>
@@ -931,6 +1055,36 @@ ephemeral keys, and offers <strong>Forward Secrecy</strong>.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Packet.get_rssi">
<span class="sig-name descname"><span class="pre">get_rssi</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.get_rssi" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The physical layer <em>Received Signal Strength Indication</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Packet.get_snr">
<span class="sig-name descname"><span class="pre">get_snr</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.get_snr" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The physical layer <em>Signal-to-Noise Ratio</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Packet.get_q">
<span class="sig-name descname"><span class="pre">get_q</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.get_q" title="Permalink to this definition">#</a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>The physical layer <em>Link Quality</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
<p id="api-packetreceipt"><h3> Packet Receipt </h3></p>
@@ -1572,8 +1726,8 @@ returning handler will not be called.</p>
</dd></dl>
<dl class="py property">
<dt class="sig sig-object py" id="RNS.Channel.Channel.MDU">
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">MDU</span></span><a class="headerlink" href="#RNS.Channel.Channel.MDU" title="Permalink to this definition">#</a></dt>
<dt class="sig sig-object py" id="RNS.Channel.Channel.mdu">
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">mdu</span></span><a class="headerlink" href="#RNS.Channel.Channel.mdu" title="Permalink to this definition">#</a></dt>
<dd><p>Maximum Data Unit: the number of bytes available
for a message to consume in a single send. This
value is adjusted from the <code class="docutils literal notranslate"><span class="pre">Link</span></code> MDU to accommodate
@@ -1814,7 +1968,10 @@ 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>
or <em>received_announce(destination_hash, announced_identity, app_data, announce_packet_hash)</em> callable. Can optionally have a
<em>receive_path_responses</em> attribute set to <code class="docutils literal notranslate"><span class="pre">True</span></code>, to also receive all path responses, in addition to live announces. See
the <a class="reference internal" href="examples.html#example-announce"><span class="std std-ref">Announce Example</span></a> for more info.</p>
</dd>
</dl>
</dd></dl>
@@ -1953,23 +2110,28 @@ will announce it.</p>
<li><a class="reference internal" href="#">API Reference</a><ul>
<li><a class="reference internal" href="#RNS.Reticulum"><code class="docutils literal notranslate"><span class="pre">Reticulum</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Reticulum.MTU"><code class="docutils literal notranslate"><span class="pre">MTU</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.LINK_MTU_DISCOVERY"><code class="docutils literal notranslate"><span class="pre">LINK_MTU_DISCOVERY</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.ANNOUNCE_CAP"><code class="docutils literal notranslate"><span class="pre">ANNOUNCE_CAP</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.MINIMUM_BITRATE"><code class="docutils literal notranslate"><span class="pre">MINIMUM_BITRATE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.get_instance"><code class="docutils literal notranslate"><span class="pre">get_instance()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.should_use_implicit_proof"><code class="docutils literal notranslate"><span class="pre">should_use_implicit_proof()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.transport_enabled"><code class="docutils literal notranslate"><span class="pre">transport_enabled()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.link_mtu_discovery"><code class="docutils literal notranslate"><span class="pre">link_mtu_discovery()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Reticulum.remote_management_enabled"><code class="docutils literal notranslate"><span class="pre">remote_management_enabled()</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Identity"><code class="docutils literal notranslate"><span class="pre">Identity</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Identity.CURVE"><code class="docutils literal notranslate"><span class="pre">CURVE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.KEYSIZE"><code class="docutils literal notranslate"><span class="pre">KEYSIZE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.RATCHETSIZE"><code class="docutils literal notranslate"><span class="pre">RATCHETSIZE</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.RATCHET_EXPIRY"><code class="docutils literal notranslate"><span class="pre">RATCHET_EXPIRY</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.TRUNCATED_HASHLENGTH"><code class="docutils literal notranslate"><span class="pre">TRUNCATED_HASHLENGTH</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.recall"><code class="docutils literal notranslate"><span class="pre">recall()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.recall_app_data"><code class="docutils literal notranslate"><span class="pre">recall_app_data()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.full_hash"><code class="docutils literal notranslate"><span class="pre">full_hash()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.truncated_hash"><code class="docutils literal notranslate"><span class="pre">truncated_hash()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.get_random_hash"><code class="docutils literal notranslate"><span class="pre">get_random_hash()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.current_ratchet_id"><code class="docutils literal notranslate"><span class="pre">current_ratchet_id()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.from_bytes"><code class="docutils literal notranslate"><span class="pre">from_bytes()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.from_file"><code class="docutils literal notranslate"><span class="pre">from_file()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Identity.to_file"><code class="docutils literal notranslate"><span class="pre">to_file()</span></code></a></li>
@@ -1984,6 +2146,8 @@ will announce it.</p>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Destination"><code class="docutils literal notranslate"><span class="pre">Destination</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Destination.RATCHET_COUNT"><code class="docutils literal notranslate"><span class="pre">RATCHET_COUNT</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.RATCHET_INTERVAL"><code class="docutils literal notranslate"><span class="pre">RATCHET_INTERVAL</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.expand_name"><code class="docutils literal notranslate"><span class="pre">expand_name()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.app_and_aspects_from_name"><code class="docutils literal notranslate"><span class="pre">app_and_aspects_from_name()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Destination.hash_from_name_and_identity"><code class="docutils literal notranslate"><span class="pre">hash_from_name_and_identity()</span></code></a></li>
@@ -1996,6 +2160,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>
@@ -2011,6 +2179,9 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Packet.PLAIN_MDU"><code class="docutils literal notranslate"><span class="pre">PLAIN_MDU</span></code></a></li>
<li><a class="reference internal" href="#RNS.Packet.send"><code class="docutils literal notranslate"><span class="pre">send()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Packet.resend"><code class="docutils literal notranslate"><span class="pre">resend()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Packet.get_rssi"><code class="docutils literal notranslate"><span class="pre">get_rssi()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Packet.get_snr"><code class="docutils literal notranslate"><span class="pre">get_snr()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Packet.get_q"><code class="docutils literal notranslate"><span class="pre">get_q()</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.PacketReceipt"><code class="docutils literal notranslate"><span class="pre">PacketReceipt</span></code></a><ul>
@@ -2079,7 +2250,7 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Channel.Channel.remove_message_handler"><code class="docutils literal notranslate"><span class="pre">remove_message_handler()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.is_ready_to_send"><code class="docutils literal notranslate"><span class="pre">is_ready_to_send()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.send"><code class="docutils literal notranslate"><span class="pre">send()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.MDU"><code class="docutils literal notranslate"><span class="pre">MDU</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.mdu"><code class="docutils literal notranslate"><span class="pre">mdu</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a><ul>
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.7.6 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.9.2 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
@@ -138,7 +138,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -164,7 +164,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="#" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
File diff suppressed because one or more lines are too long
+6 -5
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Support Reticulum - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Support Reticulum - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -248,7 +248,8 @@ organisation? Make them a reality quickly by sponsoring their implementation.</p
<h2>Provide Feedback<a class="headerlink" href="#provide-feedback" title="Permalink to this heading">#</a></h2>
<p>All feedback on the usage, functioning and potential dysfunctioning of any and
all components of the system is very valuable to the continued development and
improvement of Reticulum. Absolutely no automated analytics, telemetry, error
improvement of Reticulum.</p>
<p>Absolutely no automated analytics, telemetry, error
reporting or statistics is collected and reported by Reticulum under any
circumstances, so we rely on old-fashioned human feedback.</p>
</section>
+31 -19
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Understanding Reticulum - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Understanding Reticulum - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -360,10 +360,11 @@ be sufficient, even far into the future.</p>
<p>By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
channel to a destination, called a <em>Link</em>. Both data sent over Links and single packets offer
<em>Initiator Anonymity</em>, and links additionally offer <em>Forward Secrecy</em> by using an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
coordination, verification and reliability layers are fully autonomous and also based on elliptic
curve cryptography.</p>
<em>Initiator Anonymity</em>. Links additionally offer <em>Forward Secrecy</em> by default, employing an Elliptic Curve
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
layers are fully autonomous and also based on elliptic curve cryptography.</p>
<p>Reticulum also offers symmetric key encryption for group-oriented communications, as well as
unencrypted packets for local broadcast purposes.</p>
<p>Reticulum can connect to a variety of interfaces such as radio modems, data radios and serial ports,
@@ -639,7 +640,7 @@ expect. Reticulum offers two ways to do this.</p>
<li><div class="line-block">
<div class="line">A packet is always created with an associated destination and some payload data. When the packet is sent
to a <em>single</em> destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the 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">
@@ -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,13 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
<li><p>Ed25519 for signatures</p></li>
<li><p>X25519 for ECDH key exchanges</p></li>
<li><p>HKDF for key derivation</p></li>
<li><p>Fernet for encrypted tokens</p>
<li><p>Encrypted tokens are based on the Fernet spec</p>
<ul>
<li><p>AES-128 in CBC mode</p></li>
<li><p>HMAC for message authentication</p></li>
<li><p>Ephemeral keys derived from an ECDH key exchange on Curve25519</p></li>
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for message authentication</p></li>
<li><p>IVs are generated through os.urandom()</p></li>
<li><p>No Fernet version and timestamp metadata fields</p></li>
</ul>
</li>
<li><p>SHA-256</p></li>
@@ -1076,12 +1085,12 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
primitives are provided by <a class="reference external" href="https://www.openssl.org/">OpenSSL</a> (via the <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>
package). The hashing functions <code class="docutils literal notranslate"><span class="pre">SHA-256</span></code> and <code class="docutils literal notranslate"><span class="pre">SHA-512</span></code> are provided by the standard
Python <a class="reference external" href="https://docs.python.org/3/library/hashlib.html">hashlib</a>. The <code class="docutils literal notranslate"><span class="pre">HKDF</span></code>, <code class="docutils literal notranslate"><span class="pre">HMAC</span></code>,
<code class="docutils literal notranslate"><span class="pre">Fernet</span></code> primitives, and the <code class="docutils literal notranslate"><span class="pre">PKCS7</span></code> padding function are always provided by the
<code class="docutils literal notranslate"><span class="pre">Token</span></code> primitives, and the <code class="docutils literal notranslate"><span class="pre">PKCS7</span></code> padding function are always provided by the
following internal implementations:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/HKDF.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/HMAC.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/Fernet.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/Token.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/PKCS7.py</span></code></p></li>
</ul>
<p>Reticulum also includes a complete implementation of all necessary primitives in pure Python.
@@ -1090,9 +1099,12 @@ instead use the internal pure-python primitives. A trivial consequence of this i
with the OpenSSL backend being <em>much</em> faster. The most important consequence however, is the
potential loss of security by using primitives that has not seen the same amount of scrutiny,
testing and review as those from OpenSSL.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>If you want to use the internal pure-python primitives, it is <strong>highly advisable</strong> that you
have a good understanding of the risks that this pose, and make an informed decision on whether
those risks are acceptable to you.</p>
</div>
</section>
</section>
</section>
+94 -31
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 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
@@ -1037,6 +1099,7 @@ systemctl --user enable rnsd.service
<li><a class="reference internal" href="#the-rnodeconf-utility">The rnodeconf Utility</a></li>
</ul>
</li>
<li><a class="reference internal" href="#remote-management">Remote Management</a></li>
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
<li><a class="reference internal" href="#fixed-serial-port-names">Fixed Serial Port Names</a></li>
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a><ul>
+51 -24
View File
@@ -2,11 +2,11 @@
<html class="no-js" lang="en">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>What is Reticulum? - Reticulum Network Stack 0.7.6 beta documentation</title>
<title>What is Reticulum? - Reticulum Network Stack 0.9.2 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.7.6 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.2 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.7.6 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.2 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -235,7 +235,7 @@ respect and empower the autonomy and sovereignty of communities and individuals.
Reticulum enables secure digital communication that cannot be subjected to
outside control, manipulation or censorship.</p>
<p>Reticulum enables the construction of both small and potentially planetary-scale
networks, without any need for hierarchical or beaureucratic structures to control
networks, without any need for hierarchical or bureaucratic structures to control
or manage them, while ensuring individuals and communities full sovereignty
over their own network segments.</p>
<p>Reticulum is a <strong>complete networking stack</strong>, and does not need IP or higher
@@ -258,36 +258,62 @@ considered complete and stable at the moment, but could change if absolutely war
<h2>What does Reticulum Offer?<a class="headerlink" href="#what-does-reticulum-offer" title="Permalink to this heading">#</a></h2>
<ul class="simple">
<li><p>Coordination-less globally unique addressing and identification</p></li>
<li><p>Fully self-configuring multi-hop routing</p></li>
<li><p>Complete initiator anonymity, communicate without revealing your identity</p></li>
<li><p>Asymmetric encryption based on X25519, and Ed25519 signatures as a basis for all communication</p></li>
<li><p>Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519</p></li>
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
<li><p>Fully self-configuring multi-hop routing over heterogeneous carriers</p></li>
<li><p>Flexible scalability over heterogeneous topologies</p>
<ul>
<li><p>All keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
<li><p>Reticulum can carry data over any mixture of physical mediums and topologies</p></li>
<li><p>Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks</p></li>
</ul>
</li>
<li><p>Initiator anonymity, communicate without revealing your identity</p>
<ul>
<li><p>Reticulum does not include source addresses on any packets</p></li>
</ul>
</li>
<li><p>Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication</p>
<ul>
<li><p>The foundational Reticulum Identity Keys are 512-bit Elliptic Curve keysets</p></li>
</ul>
</li>
<li><p>Forward Secrecy is available for all communication types, both for single packets and over links</p></li>
<li><p>Reticulum uses the following format for encrypted tokens:</p>
<ul>
<li><p>Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519</p></li>
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for authentication</p></li>
<li><p>IVs are generated through os.urandom()</p></li>
</ul>
</li>
<li><p>Unforgeable packet delivery confirmations</p></li>
<li><p>A variety of supported interface types</p></li>
<li><p>An intuitive and developer-friendly API</p></li>
<li><p>Flexible and extensible interface system</p>
<ul>
<li><p>Reticulum includes a large variety of built-in interface types</p></li>
<li><p>Ability to load and utilise custom user- or community-supplied interface types</p></li>
<li><p>Easily create your own custom interfaces for communicating over anything</p></li>
</ul>
</li>
<li><p>Authentication and virtual network segmentation on all supported interface types</p></li>
<li><p>An intuitive and easy-to-use API</p>
<ul>
<li><p>Simpler and easier to use than sockets APIs and simpler, but more powerful</p></li>
<li><p>Makes building distributed and decentralised applications much simpler</p></li>
</ul>
</li>
<li><p>Reliable and efficient transfer of arbitrary amounts of data</p>
<ul>
<li><p>Reticulum can handle a few bytes of data or files of many gigabytes</p></li>
<li><p>Sequencing, compression, transfer coordination and checksumming are automatic</p></li>
<li><p>The API is very easy to use, and provides transfer progress</p></li>
</ul>
</li>
<li><p>Lightweight, flexible and expandable Request/Response mechanism</p></li>
<li><p>Efficient link establishment</p>
<ul>
<li><p>Total cost of setting up an encrypted and verified link is only 3 packets, totalling 297 bytes</p></li>
<li><p>Low cost of keeping links open at only 0.44 bits per second</p></li>
</ul>
</li>
<li><p>Reliable and efficient transfer of arbitrary amounts of data</p>
<ul>
<li><p>Reticulum can handle a few bytes of data or files of many gigabytes</p></li>
<li><p>Sequencing, transfer coordination and checksumming is automatic</p></li>
<li><p>The API is very easy to use, and provides transfer progress</p></li>
</ul>
</li>
<li><p>Authentication and virtual network segmentation on all supported interface types</p></li>
<li><p>Flexible scalability allowing extremely low-bandwidth networks to co-exist and interoperate with large, high-bandwidth networks</p></li>
<li><p>Reliable sequential delivery with Channel and Buffer mechanisms</p></li>
</ul>
</section>
<section id="where-can-reticulum-be-used">
@@ -300,7 +326,8 @@ of the types of interfaces Reticulum was designed for.</p>
<p>An open-source LoRa-based interface called <a class="reference external" href="https://unsigned.io/rnode">RNode</a>
has been designed as an example transceiver that is very suitable for
Reticulum. It is possible to build it yourself, to transform a common LoRa
development board into one, or it can be purchased as a complete transceiver.</p>
development board into one, or it can be purchased as a complete transceiver
from various vendors.</p>
<p>Reticulum can also be encapsulated over existing IP networks, so 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
@@ -314,7 +341,7 @@ network, and vice versa.</p>
</section>
<section id="interface-types-and-devices">
<h2>Interface Types and Devices<a class="headerlink" href="#interface-types-and-devices" title="Permalink to this heading">#</a></h2>
<p>Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, its relatively simple to implement an interface class. Currently, Reticulum can use the following devices and communication mediums:</p>
<p>Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, its simple to <a class="reference internal" href="examples.html#example-custominterface"><span class="std std-ref">implement an interface class</span></a>. Currently, Reticulum can use the following devices and communication mediums:</p>
<ul class="simple">
<li><p>Any Ethernet device</p>
<ul>
+15 -1
View File
@@ -125,4 +125,18 @@ interface to efficiently pass files of any size over a Reticulum :ref:`Link<api-
.. literalinclude:: ../../Examples/Filetransfer.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
.. _example-custominterface:
Custom Interfaces
=================
The *ExampleInterface* demonstrates creating custom interfaces for Reticulum.
Any number of custom interfaces can be loaded and utilised by Reticulum, and
will be fully on-par with natively included interfaces, including all supported
:ref:`interface modes<interfaces-modes>` and :ref:`common configuration options<interfaces-options>`.
.. literalinclude:: ../../Examples/ExampleInterface.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py>`_.
+221 -37
View File
@@ -27,9 +27,14 @@ and install them offline using ``pip``:
pip install ./rns-0.5.1-py3-none-any.whl
For more detailed installation instructions, please see the
:ref:`Platform-Specific Install Notes<install-guides>` section.
After installation is complete, it might be helpful to refer to the
:ref:`Using Reticulum on Your System<using-main>` chapter.
Resolving Dependency & Installation Issues
=============================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On some platforms, there may not be binary packages available for all dependencies, and
``pip`` installation may fail with an error message. In these cases, the issue can usually
be resolved by installing the development essentials packages for your platform:
@@ -100,10 +105,12 @@ You can install Nomad Network via pip:
# ... and run
nomadnet
**Please Note**: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again.
.. note::
If this is the very first time you use ``pip`` to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again. In some cases, you may even need to
manually add the ``pip`` install path to your ``PATH`` environment variable.
Sideband
^^^^^^^^
@@ -305,6 +312,20 @@ you are welcome to head over to the `GitHub discussion pages <https://github.com
and propose adding an interface for the hardware.
Creating and Using Custom Interfaces
===========================================
While Reticulum includes a flexible and broad range of built-in interfaces, these
will not cover every conceivable type of communications hardware that Reticulum
can potentially use to communicate.
It is therefore possible to easily write your own interface modules, that can be
loaded at run-time and used on-par with any of the built-in interface types.
For more information on this subject, and code examples to build on, please see
the :ref:`Configuring Interfaces<interfaces-main>` chapter.
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
@@ -318,14 +339,8 @@ The above command will install Reticulum and dependencies, and you will be
ready to import and use RNS in your own programs. The next step will most
likely be to look at some :ref:`Example Programs<examples-main>`.
For extended functionality, you can install optional dependencies:
.. code::
pip install pyserial
Further information can be found in the :ref:`API Reference<api-main>`.
The entire Reticulum API is documented in the :ref:`API Reference<api-main>`
chapter of this manual.
Participate in Reticulum Development
@@ -373,6 +388,7 @@ your first pull request, it is probably a good idea to introduce yourself on
the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussions>`_,
or ask one of the developers or maintainers for a good place to start.
.. _install-guides:
Platform-Specific Install Notes
==============================================
@@ -451,7 +467,7 @@ here at a later point. Until then you can use the `Sideband source code <https:/
ARM64
^^^^^^^^^^^^^^^^^^^^^^^^
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may need to install ``python3-dev`` before
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
installing Reticulum or programs that depend on Reticulum.
.. code::
@@ -463,16 +479,8 @@ installing Reticulum or programs that depend on Reticulum.
# Install Reticulum
python3 -m pip install rns
Raspberry Pi
^^^^^^^^^^^^^^^^^^^^^^^^^
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies.
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing some packages, and is not
detailed in this manual.
With these packages installed, ``pip`` will be able to build any missing dependencies
on your system locally.
Debian Bookworm
@@ -503,10 +511,152 @@ following section:
[global]
break-system-packages = true
Please note that the "break-system-packages" directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems.
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
option, you can use the following command:
.. code:: text
pip install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
MacOS
^^^^^^^^^^^^^^^^^^^^^^^^^
To install Reticulum on macOS, you will need to have Python and the ``pip`` package
manager installed.
Systems running macOS can vary quite widely in whether or not Python is pre-installed,
and if it is, which version is installed, and whether the ``pip`` package manager is
also installed and set up. If in doubt, you can `download and install <https://www.python.org/downloads/>`_
Python manually.
When Python and ``pip`` is available on your system, simply open a terminal window
and use one of the following commands:
.. code::
# Install Reticulum and utilities with pip:
pip3 install rns
# On some versions, you may need to use the
# flag --break-system-packages to install:
pip3 install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
Additionally, some version combinations of macOS and Python require you to
manually add your installed ``pip`` packages directory to your `PATH` environment
variable, before you can use installed commands in your terminal. Usually, adding
the following line to your shell init script (for example ``~/.zshrc``) will be enough:
.. code::
export PATH=$PATH:~/Library/Python/3.9/bin
Adjust Python version and shell init script location according to your system.
OpenWRT
^^^^^^^^^^^^^^^^^^^^^^^^^
On OpenWRT systems with sufficient storage and memory, you can install
Reticulum and related utilities using the `opkg` package manager and `pip`.
.. note::
At the time of releasing this manual, work is underway to create pre-built
Reticulum packages for OpenWRT, with full configuration, service
and ``uci`` integration. Please see the `feed-reticulum <https://github.com/gretel/feed-reticulum>`_
and `reticulum-openwrt <https://github.com/gretel/reticulum-openwrt>`_
repositories for more information.
To install Reticulum on OpenWRT, first log into a command line session, and
then use the following instructions:
.. code::
# Install dependencies
opkg install python3 python3-pip python3-cryptography python3-pyserial
# Install Reticulum
pip install rns
# Start rnsd with debug logging enabled
rnsd -vvv
.. note::
The above instructions have been verified and tested on OpenWRT 21.02 only.
It is likely that other versions may require slightly altered installation
commands or package names. You will also need enough free space in your
overlay FS, and enough free RAM to actually run Reticulum and any related
programs and utilities.
Depending on your device configuration, you may need to adjust firewall rules
for Reticulum connectivity to and from your device to work. Until proper
packaging is ready, you will also need to manually create a service or startup
script to automatically laucnh Reticulum at boot time.
Please also note that the `AutoInterface` requires link-local IPv6 addresses
to be enabled for any Ethernet and WiFi devices you intend to use. If ``ip a``
shows an address starting with ``fe80::`` for the device in question,
``AutoInterface`` should work for that device.
Raspberry Pi
^^^^^^^^^^^^^^^^^^^^^^^^^
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies. If Python and the
`pip` package manager is not already installed, do that first, and then
install Reticulum using `pip`.
.. code::
# Install dependencies
sudo apt install python3 python3-pip python3-cryptography python3-pyserial
# Install Reticulum
pip install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing required build dependencies,
and is not detailed in this manual.
RISC-V
^^^^^^^^^^^^^^^^^^^^^^^^
On some architectures, including RISC-V, not all dependencies have precompiled
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
installing Reticulum or programs that depend on Reticulum.
.. code::
# Install Python and development packages
sudo apt update
sudo apt install python3 python3-pip python3-dev
# Install Reticulum
python3 -m pip install rns
With these packages installed, ``pip`` will be able to build any missing dependencies
on your system locally.
Ubuntu Lunar
@@ -537,13 +687,52 @@ following section:
[global]
break-system-packages = true
Please note that the "break-system-packages" directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this _could_ in rare
cases lead to version conflicts, it does not generally pose any problems.
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
option, you can use the following command:
.. code:: text
pip install rns --break-system-packages
.. note::
The ``--break-system-packages`` directive is a somewhat misleading choice
of words. Setting it will of course not break any system packages, but will simply
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
cases lead to version conflicts, it does not generally pose any problems, especially
not in the case of installing Reticulum.
Windows
^^^^^^^^^^^^^^^^^^^^^^^^^
On Windows operating systems, the easiest way to install Reticulum is by using the
``pip`` package manager from the command line (either the command prompt or Windows
Powershell).
If you don't already have Python installed, `download and install Python <https://www.python.org/downloads/>`_.
At the time of publication of this manual, the recommended version is `Python 3.12.7 <https://www.python.org/downloads/release/python-3127>`_.
**Important!** When asked by the installer, make sure to add the Python program to
your PATH environment variables. If you don't do this, you will not be able to
use the ``pip`` installer, or run the included Reticulum utility programs (such as
``rnsd`` and ``rnstatus``) from the command line.
After installing Python, open the command prompt or Windows Powershell, and type:
.. code::
pip install rns
You can now use Reticulum and all included utility programs directly from your
preferred command line interface.
Pure-Python Reticulum
==============================================
.. warning::
If you use the ``rnspure`` package to run Reticulum on systems that
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
section of this manual.
In some rare cases, and on more obscure system types, it is not possible to
install one or more dependencies. In such situations,
you can use the ``rnspure`` package instead of the ``rns`` package, or use ``pip``
@@ -558,8 +747,3 @@ only if they are *needed* and *available*. If for example you want to use Reticu
on a system that cannot support ``pyserial``, it is perfectly possible to do so using
the `rnspure` package, but Reticulum will not be able to use serial-based interfaces.
All other available modules will still be loaded when needed.
**Please Note!** If you use the `rnspure` package to run Reticulum on systems that
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
section of this manual.

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

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