mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-23 20:34:29 -07:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 817ee0721a | |||
| 22ec4afdab | |||
| 61626897e7 | |||
| 6fd3edbb8f | |||
| fc5b02ed5d | |||
| a06e752b76 | |||
| 3a947bf81b | |||
| 31121ca885 | |||
| 387b8c46ff | |||
| 66fda34b20 | |||
| 1542c5f4fe | |||
| 523fc7b8f9 | |||
| 73faf04ea1 | |||
| e10ddf9d2d | |||
| 641a7ea75d | |||
| e543d5c27f | |||
| 01c59ab0c6 | |||
| a4c64abed4 | |||
| 7df11a6f67 | |||
| 1bd6020163 | |||
| b3ac3131b5 | |||
| f522cb1db1 | |||
| d96a4853fe | |||
| 52a0447fea | |||
| e82e6d56f1 | |||
| 3967ef453d | |||
| 76f7751d5f | |||
| 8716ffc873 | |||
| b476e4cfb0 | |||
| 7ec77a10d3 | |||
| 55a9c5ef71 | |||
| 6d3ba31993 | |||
| d3f4a674aa | |||
| 599ab20ed0 | |||
| dcf33e125b | |||
| 01600b96a4 | |||
| 64bdc4c18c | |||
| 0889b8a7c5 | |||
| 1b2fee3ab8 | |||
| da7a4433c0 | |||
| 5e5d89cc92 | |||
| a3bee4baa9 | |||
| fab83ec399 | |||
| b740e36985 | |||
| 29693c6fe2 | |||
| 72638f40a6 | |||
| 8d29e83d90 | |||
| 53b325d34d | |||
| d31cf6e297 | |||
| e386a5d08b | |||
| d467ed9ece | |||
| 892a467d74 | |||
| 4366e71f34 | |||
| 7e9998b4fd | |||
| 79abe93139 | |||
| d69d4b3920 | |||
| 3300541181 | |||
| 3848059f19 | |||
| 30021d89cb | |||
| 29019724bd | |||
| ba7838c04e | |||
| af16c68e47 | |||
| bda5717051 | |||
| fac4973329 |
@@ -10,5 +10,6 @@ docs/build
|
||||
rns*.egg-info
|
||||
profile.data
|
||||
tests/rnsconfig/storage
|
||||
tests/rnsconfig/logfile
|
||||
*.data
|
||||
*.result
|
||||
|
||||
@@ -1,3 +1,47 @@
|
||||
### 2023-05-12: RNS β 0.5.2
|
||||
|
||||
This maintenance release brings a number of bugfixes and improvements.
|
||||
|
||||
**Important!** This release breaks backwards compatibility with `Channel` and `Buffer` for all previous releases, due to the addition of compression and windowing.
|
||||
|
||||
**Changes**
|
||||
- Added ability to trust external signing keys to `rnodeconf`
|
||||
- Added basic windowing to `Channel` and `Buffer`, improving performance over faster links
|
||||
- Added per-packet compression to `Channel`
|
||||
- Added automatic multi-interface duplicate deque to AutoInterface
|
||||
- Fixed received link packet proofs not resetting link watchdog stale timer
|
||||
- Fixed a missing exception isolation of packet delivery callbacks
|
||||
- Fixed resent packets not getting repacked
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
f3b1e9cf39420ad74c2b5c81ad339fd2a548320c9f6925bad9b614feb4c9b9d7 rns-0.5.2-py3-none-any.whl
|
||||
8463f7365f179d02e7e4d4fe4afc69da4218ce40107305dfd06b9e6b29513e0f rnspure-0.5.2-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-05-05: RNS β 0.5.1
|
||||
|
||||
This maintenance release brings a number of bugfixes and improvements. Thanks to @VioletEternity, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Removed dependency on netifaces module
|
||||
- Added ability to configure RNode display intensity to rnodeconf
|
||||
- Added preliminary rnodeconf flasher/autoinstaller support for T3 v1.0 boards
|
||||
- Fixed a bug that caused AutoInterface discovery scopes to fail
|
||||
- Fixed rnodeconf firmware extraction for unverifiable devices
|
||||
- Improved setting rnsd verbosity from command line
|
||||
- Improved support for shared instances on Windows
|
||||
- Improved rnodeconf support on Windows
|
||||
- Improved rnodeconf zip-file handling
|
||||
- Fixed a potentail race condition in announce queue handling for AutoInterface
|
||||
- Various minor bugfixes
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
01d76e03f93e427d9c0b95ab5d07e84ed39047e912b8afa6d619a65ac6b5e05b rns-0.5.1-py3-none-any.whl
|
||||
2cfe431bec1160410b80bbcbf87eb2ab0d5abe5c6546f41eaf3f0f5faf9b2140 rnspure-0.5.1-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-03-08: RNS β 0.5.0
|
||||
|
||||
This release brings two major new additions to the Reticulum API: The Channel and Buffer classes, that provides reliable delivery, and streams over Reticulum. Thanks to @acehoss, @erethon, @gdt and @faragher, who contributed to this release!
|
||||
|
||||
@@ -190,7 +190,6 @@ these dependencies, and when the `rns` package is installed with `pip`, they
|
||||
will be downloaded and installed as well.
|
||||
|
||||
- [PyCA/cryptography](https://github.com/pyca/cryptography)
|
||||
- [netifaces](https://github.com/al45tair/netifaces)
|
||||
- [pyserial](https://github.com/pyserial/pyserial)
|
||||
|
||||
On more unusual systems, and in some rare cases, it might not be possible to
|
||||
@@ -228,31 +227,25 @@ I2P. Just add one of the following interfaces to your Reticulum configuration
|
||||
file:
|
||||
|
||||
```
|
||||
# TCP/IP interface to the Dublin Hub
|
||||
# TCP/IP interface to the RNS Dublin Hub
|
||||
[[RNS Testnet Dublin]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = dublin.connect.reticulum.network
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt Hub
|
||||
[[RNS Testnet Frankfurt]]
|
||||
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
|
||||
[[RNS Testnet BetweenTheBorders]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
target_port = 5377
|
||||
target_host = betweentheborders.com
|
||||
target_port = 4242
|
||||
|
||||
# Interface to I2P Hub A
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
# Interface to Testnet I2P Hub
|
||||
[[RNS Testnet I2P Hub]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = pmlm3l5rpympihoy2o5ago43kluei2jjjzsalcuiuylbve3mwi2a.b32.i2p
|
||||
|
||||
# Interface to I2P Hub B
|
||||
[[RNS Testnet I2P Hub B]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = iwoqtz22dsr73aemwpw7guocplsjjoamyl7sogj33qtcd6ds4mza.b32.i2p
|
||||
```
|
||||
|
||||
The testnet also contains a number of [Nomad Network](https://github.com/markqvist/nomadnet) nodes, and LXMF propagation nodes.
|
||||
@@ -341,8 +334,8 @@ projects:
|
||||
- [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
|
||||
- [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
|
||||
- [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
|
||||
- [Netifaces](https://github.com/al45tair/netifaces) by [Alastair Houghton](https://github.com/al45tair), *MIT License*
|
||||
- [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
|
||||
- [Six](https://github.com/benjaminp/six) by [Benjamin Peterson](https://github.com/benjaminp), *MIT License*
|
||||
- [ifaddr](https://github.com/pydron/ifaddr) by [Pydron](https://github.com/pydron), *MIT License*
|
||||
- [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
|
||||
- [Python](https://www.python.org)
|
||||
|
||||
+18
-6
@@ -1,4 +1,5 @@
|
||||
from __future__ import annotations
|
||||
import bz2
|
||||
import sys
|
||||
import threading
|
||||
from threading import RLock
|
||||
@@ -9,7 +10,6 @@ from io import RawIOBase, BufferedRWPair, BufferedReader, BufferedWriter
|
||||
from typing import Callable
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
|
||||
class StreamDataMessage(MessageBase):
|
||||
MSGTYPE = SystemMessageTypes.SMT_STREAM_DATA
|
||||
"""
|
||||
@@ -17,9 +17,9 @@ class StreamDataMessage(MessageBase):
|
||||
uses a system-reserved message type.
|
||||
"""
|
||||
|
||||
STREAM_ID_MAX = 0x7fff # 32767
|
||||
STREAM_ID_MAX = 0x3fff # 16383
|
||||
"""
|
||||
The stream id is limited to 2 bytes - 1 bit
|
||||
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
|
||||
@@ -39,22 +39,34 @@ class StreamDataMessage(MessageBase):
|
||||
"""
|
||||
super().__init__()
|
||||
if stream_id is not None and stream_id > self.STREAM_ID_MAX:
|
||||
raise ValueError("stream_id must be 0-32767")
|
||||
raise ValueError("stream_id must be 0-16383")
|
||||
self.stream_id = stream_id
|
||||
self.compressed = False
|
||||
self.data = data or bytes()
|
||||
self.eof = eof
|
||||
|
||||
def pack(self) -> bytes:
|
||||
if self.stream_id is None:
|
||||
raise ValueError("stream_id")
|
||||
header_val = (0x7fff & self.stream_id) | (0x8000 if self.eof else 0x0000)
|
||||
|
||||
compressed_data = bz2.compress(self.data)
|
||||
saved = len(self.data)-len(compressed_data)
|
||||
|
||||
if saved > 0:
|
||||
self.data = compressed_data
|
||||
self.compressed = True
|
||||
|
||||
header_val = (0x3fff & self.stream_id) | (0x8000 if self.eof else 0x0000) | (0x4000 if self.compressed > 0 else 0x0000)
|
||||
return bytes(struct.pack(">H", header_val) + (self.data if self.data else bytes()))
|
||||
|
||||
def unpack(self, raw):
|
||||
self.stream_id = struct.unpack(">H", raw[:2])[0]
|
||||
self.eof = (0x8000 & self.stream_id) > 0
|
||||
self.stream_id = self.stream_id & 0x7fff
|
||||
self.compressed = (0x4000 & self.stream_id) > 0
|
||||
self.stream_id = self.stream_id & 0x3fff
|
||||
self.data = raw[2:]
|
||||
if self.compressed:
|
||||
self.data = bz2.decompress(self.data)
|
||||
|
||||
|
||||
class RawChannelReader(RawIOBase, AbstractContextManager):
|
||||
|
||||
+148
-35
@@ -223,6 +223,44 @@ class Channel(contextlib.AbstractContextManager):
|
||||
``Channel`` is not instantiated directly, but rather
|
||||
obtained from a ``Link`` with ``get_channel()``.
|
||||
"""
|
||||
|
||||
# The initial window size at channel setup
|
||||
WINDOW = 2
|
||||
|
||||
# Absolute minimum window size
|
||||
WINDOW_MIN = 1
|
||||
|
||||
# The maximum window size for transfers on slow links
|
||||
WINDOW_MAX_SLOW = 5
|
||||
|
||||
# The maximum window size for transfers on mid-speed links
|
||||
WINDOW_MAX_MEDIUM = 16
|
||||
|
||||
# The maximum window size for transfers on fast links
|
||||
WINDOW_MAX_FAST = 48
|
||||
|
||||
# For calculating maps and guard segments, this
|
||||
# must be set to the global maximum window.
|
||||
WINDOW_MAX = WINDOW_MAX_FAST
|
||||
|
||||
# If the fast rate is sustained for this many request
|
||||
# rounds, the fast link window size will be allowed.
|
||||
FAST_RATE_THRESHOLD = 10
|
||||
|
||||
# If the RTT rate is higher than this value,
|
||||
# the max window size for fast links will be used.
|
||||
RTT_FAST = 0.25
|
||||
RTT_MEDIUM = 0.75
|
||||
RTT_SLOW = 1.45
|
||||
|
||||
# The minimum allowed flexibility of the window size.
|
||||
# The difference between window_max and window_min
|
||||
# will never be smaller than this value.
|
||||
WINDOW_FLEXIBILITY = 4
|
||||
|
||||
SEQ_MAX = 0xFFFF
|
||||
SEQ_MODULUS = SEQ_MAX+1
|
||||
|
||||
def __init__(self, outlet: ChannelOutletBase):
|
||||
"""
|
||||
|
||||
@@ -234,8 +272,22 @@ class Channel(contextlib.AbstractContextManager):
|
||||
self._rx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._message_callbacks: [MessageCallbackType] = []
|
||||
self._next_sequence = 0
|
||||
self._next_rx_sequence = 0
|
||||
self._message_factories: dict[int, Type[MessageBase]] = {}
|
||||
self._max_tries = 5
|
||||
self.fast_rate_rounds = 0
|
||||
self.medium_rate_rounds = 0
|
||||
|
||||
if self._outlet.rtt > Channel.RTT_SLOW:
|
||||
self.window = 1
|
||||
self.window_max = 1
|
||||
self.window_min = 1
|
||||
self.window_flexibility = 1
|
||||
else:
|
||||
self.window = Channel.WINDOW
|
||||
self.window_max = Channel.WINDOW_MAX_SLOW
|
||||
self.window_min = Channel.WINDOW_MIN
|
||||
self.window_flexibility = Channel.WINDOW_FLEXIBILITY
|
||||
|
||||
def __enter__(self) -> Channel:
|
||||
return self
|
||||
@@ -325,21 +377,13 @@ class Channel(contextlib.AbstractContextManager):
|
||||
ring.insert(i, envelope)
|
||||
return True
|
||||
if existing.sequence == envelope.sequence:
|
||||
RNS.log(f"Envelope: Emplacement of duplicate envelope sequence.", RNS.LOG_EXTREME)
|
||||
RNS.log(f"Envelope: Emplacement of duplicate envelope with sequence "+str(envelope.sequence), RNS.LOG_EXTREME)
|
||||
return False
|
||||
i += 1
|
||||
envelope.tracked = True
|
||||
ring.append(envelope)
|
||||
return True
|
||||
|
||||
def _prune_rx_ring(self):
|
||||
with self._lock:
|
||||
# Implementation for fixed window = 1
|
||||
stale = list(sorted(self._rx_ring, key=lambda env: env.sequence, reverse=True))[1:]
|
||||
for env in stale:
|
||||
env.tracked = False
|
||||
self._rx_ring.remove(env)
|
||||
|
||||
def _run_callbacks(self, message: MessageBase):
|
||||
with self._lock:
|
||||
cbs = self._message_callbacks.copy()
|
||||
@@ -348,27 +392,46 @@ class Channel(contextlib.AbstractContextManager):
|
||||
try:
|
||||
if cb(message):
|
||||
return
|
||||
except Exception as ex:
|
||||
RNS.log(f"Channel: Error running message callback: {ex}", RNS.LOG_ERROR)
|
||||
except Exception as e:
|
||||
RNS.log("Channel "+str(self)+" experienced an error while running a message callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def _receive(self, raw: bytes):
|
||||
try:
|
||||
envelope = Envelope(outlet=self._outlet, raw=raw)
|
||||
with self._lock:
|
||||
message = envelope.unpack(self._message_factories)
|
||||
prev_env = self._rx_ring[0] if len(self._rx_ring) > 0 else None
|
||||
if prev_env and envelope.sequence != (prev_env.sequence + 1) % 0x10000:
|
||||
RNS.log("Channel: Out of order packet received", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
if envelope.sequence < self._next_rx_sequence:
|
||||
window_overflow = (self._next_rx_sequence+Channel.WINDOW_MAX) % Channel.SEQ_MODULUS
|
||||
if window_overflow < self._next_rx_sequence:
|
||||
if envelope.sequence > window_overflow:
|
||||
RNS.log("Invalid packet sequence ("+str(envelope.sequence)+") received on channel "+str(self), RNS.LOG_DEBUG)
|
||||
return
|
||||
else:
|
||||
if envelope.sequence < self._next_rx_sequence:
|
||||
RNS.log("Invalid packet sequence ("+str(envelope.sequence)+") received on channel "+str(self), RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
is_new = self._emplace_envelope(envelope, self._rx_ring)
|
||||
self._prune_rx_ring()
|
||||
|
||||
if not is_new:
|
||||
RNS.log("Channel: Duplicate message received", RNS.LOG_DEBUG)
|
||||
RNS.log("Duplicate message received on channel "+str(self), RNS.LOG_EXTREME)
|
||||
return
|
||||
RNS.log(f"Message received: {message}", RNS.LOG_DEBUG)
|
||||
threading.Thread(target=self._run_callbacks, name="Message Callback", args=[message], daemon=True).start()
|
||||
except Exception as ex:
|
||||
RNS.log(f"Channel: Error receiving data: {ex}")
|
||||
else:
|
||||
with self._lock:
|
||||
contigous = []
|
||||
for e in self._rx_ring:
|
||||
if e.sequence == self._next_rx_sequence:
|
||||
contigous.append(e)
|
||||
self._next_rx_sequence = (self._next_rx_sequence + 1) % Channel.SEQ_MODULUS
|
||||
|
||||
for e in contigous:
|
||||
m = e.unpack(self._message_factories)
|
||||
self._rx_ring.remove(e)
|
||||
threading.Thread(target=self._run_callbacks, name="Message Callback", args=[m], daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while receiving data on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def is_ready_to_send(self) -> bool:
|
||||
"""
|
||||
@@ -377,18 +440,18 @@ class Channel(contextlib.AbstractContextManager):
|
||||
:return: True if ready
|
||||
"""
|
||||
if not self._outlet.is_usable:
|
||||
RNS.log("Channel: Link is not usable.", RNS.LOG_EXTREME)
|
||||
return False
|
||||
|
||||
with self._lock:
|
||||
outstanding = 0
|
||||
for envelope in self._tx_ring:
|
||||
if envelope.outlet == self._outlet and (not envelope.packet
|
||||
or self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_SENT):
|
||||
# TODO: Check if this should be enabled with some kind of
|
||||
# rate limiting, since it currently floods log output when
|
||||
# messages are waiting.
|
||||
# RNS.log("Channel: Link has a pending message.", RNS.LOG_EXTREME)
|
||||
return False
|
||||
if envelope.outlet == self._outlet:
|
||||
if not envelope.packet or not self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_DELIVERED:
|
||||
outstanding += 1
|
||||
|
||||
if outstanding >= self.window:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _packet_tx_op(self, packet: TPacket, op: Callable[[TPacket], bool]):
|
||||
@@ -399,10 +462,42 @@ class Channel(contextlib.AbstractContextManager):
|
||||
envelope.tracked = False
|
||||
if envelope in self._tx_ring:
|
||||
self._tx_ring.remove(envelope)
|
||||
|
||||
if self.window < self.window_max:
|
||||
self.window += 1
|
||||
if (self.window - self.window_min) > (self.window_flexibility-1):
|
||||
self.window_min += 1
|
||||
|
||||
# TODO: Remove at some point
|
||||
RNS.log("Increased "+str(self)+" window to "+str(self.window), RNS.LOG_DEBUG)
|
||||
|
||||
if self._outlet.rtt != 0:
|
||||
if self._outlet.rtt > Channel.RTT_FAST:
|
||||
self.fast_rate_rounds = 0
|
||||
|
||||
if self._outlet.rtt > Channel.RTT_MEDIUM:
|
||||
self.medium_rate_rounds = 0
|
||||
|
||||
else:
|
||||
self.medium_rate_rounds += 1
|
||||
if self.window_max < Channel.WINDOW_MAX_MEDIUM and self.medium_rate_rounds == Channel.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Channel.WINDOW_MAX_MEDIUM
|
||||
# TODO: Remove at some point
|
||||
RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_EXTREME)
|
||||
|
||||
else:
|
||||
self.fast_rate_rounds += 1
|
||||
if self.window_max < Channel.WINDOW_MAX_FAST and self.fast_rate_rounds == Channel.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Channel.WINDOW_MAX_FAST
|
||||
# TODO: Remove at some point
|
||||
RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_EXTREME)
|
||||
|
||||
|
||||
|
||||
else:
|
||||
RNS.log("Channel: Envelope not found in TX ring", RNS.LOG_DEBUG)
|
||||
RNS.log("Envelope not found in TX ring for "+str(self), RNS.LOG_DEBUG)
|
||||
if not envelope:
|
||||
RNS.log("Channel: Spurious message received.", RNS.LOG_EXTREME)
|
||||
RNS.log("Spurious message received on "+str(self), RNS.LOG_EXTREME)
|
||||
|
||||
def _packet_delivered(self, packet: TPacket):
|
||||
self._packet_tx_op(packet, lambda env: True)
|
||||
@@ -413,13 +508,25 @@ class Channel(contextlib.AbstractContextManager):
|
||||
def _packet_timeout(self, packet: TPacket):
|
||||
def retry_envelope(envelope: Envelope) -> bool:
|
||||
if envelope.tries >= self._max_tries:
|
||||
RNS.log("Channel: Retry count exceeded, tearing down Link.", RNS.LOG_ERROR)
|
||||
RNS.log("Retry count exceeded on "+str(self)+", tearing down Link.", RNS.LOG_ERROR)
|
||||
self._shutdown() # start on separate thread?
|
||||
self._outlet.timed_out()
|
||||
return True
|
||||
envelope.tries += 1
|
||||
self._outlet.resend(envelope.packet)
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
|
||||
if self.window > self.window_min:
|
||||
self.window -= 1
|
||||
if self.window_max > self.window_min:
|
||||
self.window_max -= 1
|
||||
if (self.window_max - self.window) > (self.window_flexibility-1):
|
||||
self.window_max -= 1
|
||||
|
||||
# TODO: Remove at some point
|
||||
RNS.log("Decreased "+str(self)+" window to "+str(self.window), RNS.LOG_EXTREME)
|
||||
|
||||
return False
|
||||
|
||||
if self._outlet.get_packet_state(packet) != MessageState.MSGSTATE_DELIVERED:
|
||||
@@ -437,7 +544,8 @@ class Channel(contextlib.AbstractContextManager):
|
||||
if not self.is_ready_to_send():
|
||||
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
|
||||
envelope = Envelope(self._outlet, message=message, sequence=self._next_sequence)
|
||||
self._next_sequence = (self._next_sequence + 1) % 0x10000
|
||||
self._next_sequence = (self._next_sequence + 1) % Channel.SEQ_MODULUS
|
||||
|
||||
self._emplace_envelope(envelope, self._tx_ring)
|
||||
if envelope is None:
|
||||
raise BlockingIOError()
|
||||
@@ -449,6 +557,7 @@ class Channel(contextlib.AbstractContextManager):
|
||||
envelope.tries += 1
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
|
||||
return envelope
|
||||
|
||||
@property
|
||||
@@ -483,7 +592,8 @@ class LinkChannelOutlet(ChannelOutletBase):
|
||||
|
||||
def resend(self, packet: RNS.Packet) -> RNS.Packet:
|
||||
RNS.log("Resending packet " + RNS.prettyhexrep(packet.packet_hash), RNS.LOG_DEBUG)
|
||||
if not packet.resend():
|
||||
receipt = packet.resend()
|
||||
if not receipt:
|
||||
RNS.log("Failed to resend packet", RNS.LOG_ERROR)
|
||||
return packet
|
||||
|
||||
@@ -538,4 +648,7 @@ class LinkChannelOutlet(ChannelOutletBase):
|
||||
packet.receipt.set_delivery_callback(inner if callback else None)
|
||||
|
||||
def get_packet_id(self, packet: RNS.Packet) -> any:
|
||||
return packet.get_hash()
|
||||
if packet and hasattr(packet, "get_hash") and callable(packet.get_hash):
|
||||
return packet.get_hash()
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1003,7 +1003,7 @@ class RNodeInterface(Interface):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from collections import deque
|
||||
import socketserver
|
||||
import threading
|
||||
import re
|
||||
@@ -50,6 +51,8 @@ class AutoInterface(Interface):
|
||||
|
||||
BITRATE_GUESS = 10*1000*1000
|
||||
|
||||
MULTI_IF_DEQUE_LEN = 64
|
||||
|
||||
def handler_factory(self, callback):
|
||||
def create_handler(*args, **keys):
|
||||
return AutoInterfaceHandler(callback, *args, **keys)
|
||||
@@ -62,16 +65,18 @@ class AutoInterface(Interface):
|
||||
link_local_addr = re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr)
|
||||
return link_local_addr
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
else:
|
||||
RNS.log("Using AutoInterface requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
def list_interfaces(self):
|
||||
ifs = self.netinfo.interfaces()
|
||||
return ifs
|
||||
|
||||
def list_addresses(self, ifname):
|
||||
ifas = self.netinfo.ifaddresses(ifname)
|
||||
return ifas
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
from RNS.vendor.ifaddr import niwrapper
|
||||
self.netinfo = niwrapper
|
||||
|
||||
self.netifaces = netifaces
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
@@ -87,6 +92,7 @@ class AutoInterface(Interface):
|
||||
self.interface_servers = {}
|
||||
self.multicast_echoes = {}
|
||||
self.timed_out_interfaces = {}
|
||||
self.mif_deque = deque(maxlen=AutoInterface.MULTI_IF_DEQUE_LEN)
|
||||
self.carrier_changed = False
|
||||
|
||||
self.outbound_udp_socket = None
|
||||
@@ -148,7 +154,7 @@ class AutoInterface(Interface):
|
||||
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
|
||||
|
||||
suitable_interfaces = 0
|
||||
for ifname in self.netifaces.interfaces():
|
||||
for ifname in self.list_interfaces():
|
||||
if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
|
||||
@@ -163,10 +169,10 @@ class AutoInterface(Interface):
|
||||
if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME)
|
||||
else:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
addresses = self.list_addresses(ifname)
|
||||
if self.netinfo.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
for address in addresses[self.netinfo.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = self.descope_linklocal(address["addr"])
|
||||
@@ -196,7 +202,11 @@ class AutoInterface(Interface):
|
||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
|
||||
|
||||
# Bind socket
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
if self.discovery_scope == AutoInterface.SCOPE_LINK:
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
else:
|
||||
addr_info = socket.getaddrinfo(mcast_addr, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
|
||||
discovery_socket.bind(addr_info[0][4])
|
||||
|
||||
# Set up thread for discovery packets
|
||||
@@ -214,6 +224,11 @@ class AutoInterface(Interface):
|
||||
else:
|
||||
self.receives = True
|
||||
|
||||
if configured_bitrate != None:
|
||||
self.bitrate = configured_bitrate
|
||||
else:
|
||||
self.bitrate = AutoInterface.BITRATE_GUESS
|
||||
|
||||
peering_wait = self.announce_interval*1.2
|
||||
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
|
||||
|
||||
@@ -238,11 +253,6 @@ class AutoInterface(Interface):
|
||||
|
||||
time.sleep(peering_wait)
|
||||
|
||||
if configured_bitrate != None:
|
||||
self.bitrate = configured_bitrate
|
||||
else:
|
||||
self.bitrate = AutoInterface.BITRATE_GUESS
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
@@ -283,10 +293,10 @@ class AutoInterface(Interface):
|
||||
for ifname in self.adopted_interfaces:
|
||||
# Check that the link-local address has not changed
|
||||
try:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
addresses = self.list_addresses(ifname)
|
||||
if self.netinfo.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
for address in addresses[self.netinfo.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = self.descope_linklocal(address["addr"])
|
||||
@@ -385,8 +395,11 @@ class AutoInterface(Interface):
|
||||
self.peers[addr][1] = time.time()
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
data_hash = RNS.Identity.full_hash(data)
|
||||
if not data_hash in self.mif_deque:
|
||||
self.mif_deque.append(data_hash)
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
for peer in self.peers:
|
||||
|
||||
@@ -41,7 +41,13 @@ class HDLC():
|
||||
return data
|
||||
|
||||
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
def server_bind(self):
|
||||
if RNS.vendor.platformutils.is_windows():
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
|
||||
else:
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind(self.server_address)
|
||||
self.server_address = self.socket.getsockname()
|
||||
|
||||
class LocalClientInterface(Interface):
|
||||
RECONNECT_WAIT = 3
|
||||
@@ -299,7 +305,6 @@ class LocalServerInterface(Interface):
|
||||
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
|
||||
@@ -631,7 +631,7 @@ class RNodeInterface(Interface):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
|
||||
@@ -408,25 +408,15 @@ class TCPServerInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
|
||||
self.rxb = 0
|
||||
|
||||
@@ -34,25 +34,15 @@ class UDPInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
self.rxb = 0
|
||||
|
||||
+16
-1
@@ -148,6 +148,7 @@ class Link:
|
||||
self.pending_requests = []
|
||||
self.last_inbound = 0
|
||||
self.last_outbound = 0
|
||||
self.last_proof = 0
|
||||
self.tx = 0
|
||||
self.rx = 0
|
||||
self.txbytes = 0
|
||||
@@ -277,6 +278,7 @@ class Link:
|
||||
self.__remote_identity = self.destination.identity
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
self.last_proof = self.activated_at
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
|
||||
|
||||
@@ -527,7 +529,7 @@ class Link:
|
||||
|
||||
elif self.status == Link.ACTIVE:
|
||||
activated_at = self.activated_at if self.activated_at != None else 0
|
||||
last_inbound = max(self.last_inbound, activated_at)
|
||||
last_inbound = max(max(self.last_inbound, self.last_proof), activated_at)
|
||||
|
||||
if time.time() >= last_inbound + self.keepalive:
|
||||
if self.initiator:
|
||||
@@ -809,6 +811,19 @@ class Link:
|
||||
if not self._channel:
|
||||
RNS.log(f"Channel data received without open channel", RNS.LOG_DEBUG)
|
||||
else:
|
||||
# TODO: Remove packet loss simulator ######
|
||||
# if not hasattr(self, "drop_counter"):
|
||||
# self.drop_counter = 0
|
||||
# self.drop_counter += 1
|
||||
|
||||
# if self.drop_counter%6 == 0:
|
||||
# RNS.log("Dropping channel packet for testing", RNS.LOG_DEBUG)
|
||||
# else:
|
||||
# packet.prove()
|
||||
# plaintext = self.decrypt(packet.data)
|
||||
# self._channel._receive(plaintext)
|
||||
############################################
|
||||
|
||||
packet.prove()
|
||||
plaintext = self.decrypt(packet.data)
|
||||
self._channel._receive(plaintext)
|
||||
|
||||
+14
-4
@@ -141,7 +141,7 @@ 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 | self.packet_type
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
|
||||
else:
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
|
||||
return packed_flags
|
||||
@@ -172,8 +172,8 @@ class Packet:
|
||||
# Packet proofs over links are not encrypted
|
||||
self.ciphertext = self.data
|
||||
elif self.context == Packet.RESOURCE:
|
||||
# A resource takes care of symmetric
|
||||
# encryption by itself
|
||||
# A resource takes care of encryption
|
||||
# by itself
|
||||
self.ciphertext = self.data
|
||||
elif self.context == Packet.KEEPALIVE:
|
||||
# Keepalive packets contain no actual
|
||||
@@ -276,6 +276,10 @@ class Packet:
|
||||
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
|
||||
"""
|
||||
if self.sent:
|
||||
# Re-pack the packet to obtain new ciphertext for
|
||||
# encrypted destinations
|
||||
self.pack()
|
||||
|
||||
if RNS.Transport.outbound(self):
|
||||
return self.receipt
|
||||
else:
|
||||
@@ -396,9 +400,15 @@ class PacketReceipt:
|
||||
self.proved = True
|
||||
self.concluded_at = time.time()
|
||||
self.proof_packet = proof_packet
|
||||
link.last_proof = self.concluded_at
|
||||
|
||||
if self.callbacks.delivery != None:
|
||||
self.callbacks.delivery(self)
|
||||
try:
|
||||
self.callbacks.delivery(self)
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ class Resource:
|
||||
resource.total_parts = int(math.ceil(resource.size/float(Resource.SDU)))
|
||||
resource.received_count = 0
|
||||
resource.outstanding_parts = 0
|
||||
resource.parts = [None] * resource.total_parts
|
||||
resource.parts = [None] * resource.total_parts
|
||||
resource.window = Resource.WINDOW
|
||||
resource.window_max = Resource.WINDOW_MAX_SLOW
|
||||
resource.window_min = Resource.WINDOW_MIN
|
||||
|
||||
+4
-1
@@ -167,7 +167,7 @@ class Reticulum:
|
||||
RNS.exit()
|
||||
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None):
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
|
||||
"""
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
@@ -211,6 +211,7 @@ class Reticulum:
|
||||
self.ifac_salt = Reticulum.IFAC_SALT
|
||||
|
||||
self.requested_loglevel = loglevel
|
||||
self.requested_verbosity = verbosity
|
||||
if self.requested_loglevel != None:
|
||||
if self.requested_loglevel > RNS.LOG_EXTREME:
|
||||
self.requested_loglevel = RNS.LOG_EXTREME
|
||||
@@ -337,6 +338,8 @@ class Reticulum:
|
||||
value = self.config["logging"][option]
|
||||
if option == "loglevel" and self.requested_loglevel == None:
|
||||
RNS.loglevel = int(value)
|
||||
if self.requested_verbosity != None:
|
||||
RNS.loglevel += self.requested_verbosity
|
||||
if RNS.loglevel < 0:
|
||||
RNS.loglevel = 0
|
||||
if RNS.loglevel > 7:
|
||||
|
||||
+1
-1
@@ -1584,7 +1584,7 @@ class Transport:
|
||||
if (RNS.Reticulum.transport_enabled() or from_local_client or proof_for_local_client) and packet.destination_hash in Transport.reverse_table:
|
||||
reverse_entry = Transport.reverse_table.pop(packet.destination_hash)
|
||||
if packet.receiving_interface == reverse_entry[1]:
|
||||
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_DEBUG)
|
||||
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[2:]
|
||||
|
||||
@@ -88,7 +88,7 @@ def main():
|
||||
|
||||
parser.add_argument("-b", "--base64", action="store_true", default=False, help=argparse.SUPPRESS) # help="Use base64-encoded input and output")
|
||||
|
||||
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
|
||||
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
+212
-41
@@ -25,6 +25,7 @@
|
||||
from time import sleep
|
||||
import argparse
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
@@ -32,6 +33,7 @@ import datetime
|
||||
import time
|
||||
import math
|
||||
import hashlib
|
||||
import zipfile
|
||||
from urllib.request import urlretrieve
|
||||
from importlib import util
|
||||
import RNS
|
||||
@@ -39,7 +41,7 @@ import RNS
|
||||
RNS.logtimefmt = "%H:%M:%S"
|
||||
RNS.compact_log_fmt = True
|
||||
|
||||
program_version = "2.1.1"
|
||||
program_version = "2.1.2"
|
||||
eth_addr = "0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a"
|
||||
btc_addr = "3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq"
|
||||
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
|
||||
@@ -77,6 +79,7 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_DISP_INT = 0x45
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_BT_PIN = 0x62
|
||||
CMD_BOARD = 0x47
|
||||
@@ -129,6 +132,10 @@ class ROM():
|
||||
MODEL_A2 = 0xA2
|
||||
MODEL_A7 = 0xA7
|
||||
|
||||
PRODUCT_T32_10 = 0xB2
|
||||
MODEL_BA = 0xBA
|
||||
MODEL_BB = 0xBB
|
||||
|
||||
PRODUCT_T32_20 = 0xB0
|
||||
MODEL_B3 = 0xB3
|
||||
MODEL_B8 = 0xB8
|
||||
@@ -180,6 +187,7 @@ products = {
|
||||
ROM.PRODUCT_RNODE: "RNode",
|
||||
ROM.PRODUCT_HMBRW: "Hombrew RNode",
|
||||
ROM.PRODUCT_TBEAM: "LilyGO T-Beam",
|
||||
ROM.PRODUCT_T32_10: "LilyGO LoRa32 v1.0",
|
||||
ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0",
|
||||
ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1",
|
||||
ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2",
|
||||
@@ -207,6 +215,8 @@ models = {
|
||||
0xB8: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v20.zip"],
|
||||
0xB4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21.zip"],
|
||||
0xB9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21.zip"],
|
||||
0xBA: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v10.zip"],
|
||||
0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip"],
|
||||
0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip"],
|
||||
0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip"],
|
||||
0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip"],
|
||||
@@ -226,6 +236,7 @@ try:
|
||||
FWD_DIR = CNF_DIR+"/firmware"
|
||||
EXT_DIR = CNF_DIR+"/extracted"
|
||||
RT_PATH = CNF_DIR+"/recovery_esptool.py"
|
||||
TK_DIR = CNF_DIR+"/trusted_keys"
|
||||
|
||||
if not os.path.isdir(CNF_DIR):
|
||||
os.makedirs(CNF_DIR)
|
||||
@@ -235,6 +246,8 @@ try:
|
||||
os.makedirs(FWD_DIR)
|
||||
if not os.path.isdir(EXT_DIR):
|
||||
os.makedirs(EXT_DIR)
|
||||
if not os.path.isdir(TK_DIR):
|
||||
os.makedirs(TK_DIR)
|
||||
|
||||
except Exception as e:
|
||||
print("No access to directory "+str(CNF_DIR)+". This utility needs file system access to store firmware and data files. Cannot continue.")
|
||||
@@ -556,6 +569,13 @@ class RNode():
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending host left command to device")
|
||||
|
||||
def set_display_intensity(self, intensity):
|
||||
data = bytes([intensity & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_INT])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending bluetooth enable command to device")
|
||||
|
||||
def enable_bluetooth(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
@@ -800,6 +820,35 @@ class RNode():
|
||||
RNS.log("Could not deserialize local signing key")
|
||||
RNS.log(str(e))
|
||||
|
||||
# Try loading trusted signing key for
|
||||
# validation of devices
|
||||
if os.path.isdir(TK_DIR):
|
||||
for f in os.listdir(TK_DIR):
|
||||
if os.path.isfile(TK_DIR+"/"+f) and f.endswith(".pubkey"):
|
||||
try:
|
||||
file = open(TK_DIR+"/"+f, "rb")
|
||||
public_bytes = file.read()
|
||||
file.close()
|
||||
|
||||
try:
|
||||
public_bytes_hex = RNS.hexrep(public_bytes, delimit=False)
|
||||
|
||||
vendor_keys = []
|
||||
for known in known_keys:
|
||||
vendor_keys.append(known[1])
|
||||
|
||||
if not public_bytes_hex in vendor_keys:
|
||||
local_key_entry = ["LOCAL", public_bytes_hex]
|
||||
known_keys.append(local_key_entry)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not deserialize trusted signing key "+str(f))
|
||||
RNS.log(str(e))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not load trusted signing key"+str(f))
|
||||
|
||||
|
||||
for known in known_keys:
|
||||
vendor = known[0]
|
||||
public_hexrep = known[1]
|
||||
@@ -1090,6 +1139,8 @@ def main():
|
||||
parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off")
|
||||
parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode")
|
||||
|
||||
parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)")
|
||||
|
||||
parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode")
|
||||
parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode")
|
||||
parser.add_argument("--txp", action="store", metavar="dBm", type=int, default=None, help="TX power in dBm for TNC mode")
|
||||
@@ -1100,12 +1151,14 @@ def main():
|
||||
parser.add_argument("--eeprom-dump", action="store_true", help="Dump EEPROM to console")
|
||||
parser.add_argument("--eeprom-wipe", action="store_true", help="Unlock and wipe EEPROM")
|
||||
|
||||
parser.add_argument("-P", "--public", action="store_true", help="Display public part of signing key")
|
||||
parser.add_argument("--trust-key", action="store", metavar="hexbytes", type=str, default=None, help="Public key to trust for device verification")
|
||||
|
||||
parser.add_argument("--version", action="store_true", help="Print program version and exit")
|
||||
|
||||
parser.add_argument("-f", "--flash", action="store_true", help=argparse.SUPPRESS) # Flash firmware and bootstrap EEPROM
|
||||
parser.add_argument("-r", "--rom", action="store_true", help=argparse.SUPPRESS) # Bootstrap EEPROM without flashing firmware
|
||||
parser.add_argument("-k", "--key", action="store_true", help=argparse.SUPPRESS) # Generate a new signing key and exit
|
||||
parser.add_argument("-P", "--public", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key
|
||||
parser.add_argument("-S", "--sign", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key
|
||||
parser.add_argument("-H", "--firmware-hash", action="store", help=argparse.SUPPRESS) # Display public part of signing key
|
||||
parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help=argparse.SUPPRESS) # Platform specification for device bootstrap
|
||||
@@ -1150,7 +1203,7 @@ def main():
|
||||
if args.nocheck:
|
||||
upd_nocheck = True
|
||||
|
||||
if args.public or args.key or args.flash or args.rom or args.autoinstall:
|
||||
if args.public or args.key or args.flash or args.rom or args.autoinstall or args.trust_key:
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
@@ -1161,6 +1214,25 @@ def main():
|
||||
|
||||
clear = lambda: os.system('clear')
|
||||
|
||||
if args.trust_key:
|
||||
try:
|
||||
public_bytes = bytes.fromhex(args.trust_key)
|
||||
try:
|
||||
public_key = load_der_public_key(public_bytes, backend=default_backend())
|
||||
key_hash = hashlib.sha256(public_bytes).hexdigest()
|
||||
RNS.log("Trusting key: "+str(key_hash))
|
||||
f = open(TK_DIR+"/"+str(key_hash)+".pubkey", "wb")
|
||||
f.write(public_bytes)
|
||||
f.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not create public key from supplied data. Check that the key format is valid.")
|
||||
RNS.log(str(e))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Invalid key data supplied")
|
||||
exit(0)
|
||||
|
||||
if args.use_extracted and ((args.update and args.port != None) or args.autoinstall):
|
||||
print("")
|
||||
print("You have specified that rnodeconf should use a firmware extracted")
|
||||
@@ -1249,7 +1321,16 @@ def main():
|
||||
else:
|
||||
RNS.log("Could not detect a connected RNode")
|
||||
|
||||
if rnode.provisioned and rnode.signature_valid:
|
||||
if rnode.provisioned:
|
||||
if not rnode.signature_valid:
|
||||
print("\nThe device signature in this RNode is unknown and cannot be verified. It is still")
|
||||
print("possible to extract the firmware from it, but you should make absolutely sure that")
|
||||
print("it comes from a trusted source. It is possible that someone could have modified the")
|
||||
print("firmware. If that is the case, these modifications will propagate to any new RNodes")
|
||||
print("descendent from this one!")
|
||||
print("\nHit enter if you are sure you want to continue.")
|
||||
input()
|
||||
|
||||
if rnode.firmware_hash != None:
|
||||
extracted_hash = rnode.firmware_hash
|
||||
extracted_version = rnode.version
|
||||
@@ -1267,11 +1348,11 @@ def main():
|
||||
hash_f.close()
|
||||
|
||||
extraction_parts = [
|
||||
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
|
||||
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
|
||||
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
|
||||
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
|
||||
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
|
||||
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
|
||||
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
|
||||
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
|
||||
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
|
||||
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
|
||||
]
|
||||
import subprocess, shlex
|
||||
for part, command in extraction_parts:
|
||||
@@ -1400,8 +1481,9 @@ def main():
|
||||
print("")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0")
|
||||
print("[5] LilyGO T-Beam")
|
||||
print("[6] Heltec LoRa32 v2")
|
||||
print("[5] LilyGO LoRa32 v1.0")
|
||||
print("[6] LilyGO T-Beam")
|
||||
print("[7] Heltec LoRa32 v2")
|
||||
print(" .")
|
||||
print(" / \\ Select one of these options if you want to easily turn")
|
||||
print(" | a supported development board into an RNode.")
|
||||
@@ -1433,7 +1515,7 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 5:
|
||||
elif c_dev == 6:
|
||||
selected_product = ROM.PRODUCT_TBEAM
|
||||
clear()
|
||||
print("")
|
||||
@@ -1463,6 +1545,25 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 5:
|
||||
selected_product = ROM.PRODUCT_T32_10
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" LilyGO LoRa32 v1.0 RNode Installer")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on LoRa32 devices should currently be")
|
||||
print("considered experimental. It is not intended for production or critical use.")
|
||||
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
|
||||
print("who would like to experiment with it.")
|
||||
print("")
|
||||
print("Please Note! This device is known to have a faulty battery charging circuit,")
|
||||
print("which can result in overcharging and damaging batteries. If at all possible,")
|
||||
print("it is recommended to avoid this device.")
|
||||
print("")
|
||||
print("Hit enter if you're sure you wish to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 3:
|
||||
selected_product = ROM.PRODUCT_T32_21
|
||||
clear()
|
||||
@@ -1476,7 +1577,7 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 6:
|
||||
elif c_dev == 7:
|
||||
selected_product = ROM.PRODUCT_H32_V2
|
||||
clear()
|
||||
print("")
|
||||
@@ -1608,6 +1709,28 @@ def main():
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_10:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
print("[1] 433 MHz")
|
||||
print("[2] 868 MHz")
|
||||
print("[3] 915 MHz")
|
||||
print("[4] 923 MHz")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 4:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_BA
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model > 1:
|
||||
selected_model = ROM.MODEL_BB
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_20:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
@@ -1929,7 +2052,7 @@ def main():
|
||||
if fw_filename == "rnode_firmware_tbeam.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1947,7 +2070,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1962,10 +2085,46 @@ def main():
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_lora32v10.zip":
|
||||
if numeric_version >= 1.59:
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
"--flash_mode", "dio",
|
||||
"--flash_freq", "80m",
|
||||
"--flash_size", "4MB",
|
||||
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.boot_app0",
|
||||
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.partitions",
|
||||
]
|
||||
else:
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
"--flash_mode", "dio",
|
||||
"--flash_freq", "80m",
|
||||
"--flash_size", "4MB",
|
||||
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0",
|
||||
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_lora32v20.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1983,7 +2142,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2001,7 +2160,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_lora32v21.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2019,7 +2178,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2037,7 +2196,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_heltec32v2.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2055,7 +2214,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2073,7 +2232,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_featheresp32.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2091,7 +2250,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2109,7 +2268,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_esp32_generic.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2127,7 +2286,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2145,7 +2304,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_ng20.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2163,7 +2322,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2181,7 +2340,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_ng21.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2199,7 +2358,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2216,7 +2375,7 @@ def main():
|
||||
]
|
||||
elif fw_filename == "extracted_rnode_firmware.zip":
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2287,12 +2446,13 @@ def main():
|
||||
try:
|
||||
if fw_filename.endswith(".zip"):
|
||||
RNS.log("Decompressing firmware...")
|
||||
unzip_status = call(get_flasher_call("unzip", fw_filename))
|
||||
if unzip_status == 0:
|
||||
RNS.log("Firmware decompressed")
|
||||
else:
|
||||
RNS.log("Could not extract firmware from downloaded zip file")
|
||||
try:
|
||||
with zipfile.ZipFile(fw_src+fw_filename) as zip:
|
||||
zip.extractall(fw_src)
|
||||
except Exception as e:
|
||||
RNS.log("Could not decompress firmware from downloaded zip file")
|
||||
exit()
|
||||
RNS.log("Firmware decompressed")
|
||||
|
||||
RNS.log("Flashing RNode firmware to device on "+args.port)
|
||||
from subprocess import call
|
||||
@@ -2414,13 +2574,15 @@ def main():
|
||||
ensure_firmware_file(fw_filename)
|
||||
|
||||
if fw_filename.endswith(".zip") and not fw_filename == "extracted_rnode_firmware.zip":
|
||||
RNS.log("Extracting firmware...")
|
||||
unzip_status = call(get_flasher_call("unzip", fw_filename))
|
||||
if unzip_status == 0:
|
||||
RNS.log("Firmware extracted")
|
||||
else:
|
||||
RNS.log("Could not extract firmware from downloaded zip file")
|
||||
RNS.log("Decompressing firmware...")
|
||||
fw_src = UPD_DIR+"/"+selected_version+"/"
|
||||
try:
|
||||
with zipfile.ZipFile(fw_src+fw_filename) as zip:
|
||||
zip.extractall(fw_src)
|
||||
except Exception as e:
|
||||
RNS.log("Could not decompress firmware from downloaded zip file")
|
||||
exit()
|
||||
RNS.log("Firmware decompressed")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not obtain firmware package for your board")
|
||||
@@ -2521,6 +2683,15 @@ def main():
|
||||
RNS.log("but file could not be written to disk.")
|
||||
exit()
|
||||
|
||||
if isinstance(args.display, int):
|
||||
di = args.display
|
||||
if di < 0:
|
||||
di = 0
|
||||
if di > 255:
|
||||
di = 255
|
||||
RNS.log("Setting display intensity to "+str(di))
|
||||
rnode.set_display_intensity(di)
|
||||
|
||||
if args.bluetooth_on:
|
||||
RNS.log("Enabling Bluetooth...")
|
||||
rnode.enable_bluetooth()
|
||||
|
||||
@@ -30,15 +30,15 @@ from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
targetverbosity = verbosity-quietness
|
||||
|
||||
if service:
|
||||
targetlogdest = RNS.LOG_FILE
|
||||
targetloglevel = None
|
||||
targetverbosity = None
|
||||
else:
|
||||
targetlogdest = RNS.LOG_STDOUT
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel, logdest=targetlogdest)
|
||||
reticulum = RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest)
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
RNS.log("Started rnsd version {version} connected to another shared local instance, this is probably NOT what you want!".format(version=__version__), RNS.LOG_WARNING)
|
||||
else:
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.5.0"
|
||||
__version__ = "0.5.2"
|
||||
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from RNS.vendor.ifaddr._shared import Adapter, IP
|
||||
|
||||
if os.name == "nt":
|
||||
from RNS.vendor.ifaddr._win32 import get_adapters
|
||||
elif os.name == "posix":
|
||||
from RNS.vendor.ifaddr._posix import get_adapters
|
||||
else:
|
||||
raise RuntimeError("Unsupported Operating System: %s" % os.name)
|
||||
|
||||
__all__ = ['Adapter', 'IP', 'get_adapters']
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import os
|
||||
import ctypes.util
|
||||
import ipaddress
|
||||
import collections
|
||||
import socket
|
||||
|
||||
from typing import Iterable, Optional
|
||||
|
||||
import RNS.vendor.ifaddr._shared as shared
|
||||
|
||||
class ifaddrs(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
ifaddrs._fields_ = [
|
||||
('ifa_next', ctypes.POINTER(ifaddrs)),
|
||||
('ifa_name', ctypes.c_char_p),
|
||||
('ifa_flags', ctypes.c_uint),
|
||||
('ifa_addr', ctypes.POINTER(shared.sockaddr)),
|
||||
('ifa_netmask', ctypes.POINTER(shared.sockaddr)),
|
||||
]
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True) # type: ignore
|
||||
|
||||
|
||||
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
|
||||
|
||||
addr0 = addr = ctypes.POINTER(ifaddrs)()
|
||||
retval = libc.getifaddrs(ctypes.byref(addr))
|
||||
if retval != 0:
|
||||
eno = ctypes.get_errno()
|
||||
raise OSError(eno, os.strerror(eno))
|
||||
|
||||
ips = collections.OrderedDict()
|
||||
|
||||
def add_ip(adapter_name: str, ip: Optional[shared.IP]) -> None:
|
||||
if adapter_name not in ips:
|
||||
index = None # type: Optional[int]
|
||||
try:
|
||||
# Mypy errors on this when the Windows CI runs:
|
||||
# error: Module has no attribute "if_nametoindex"
|
||||
index = socket.if_nametoindex(adapter_name) # type: ignore
|
||||
except (OSError, AttributeError):
|
||||
pass
|
||||
ips[adapter_name] = shared.Adapter(adapter_name, adapter_name, [], index=index)
|
||||
if ip is not None:
|
||||
ips[adapter_name].ips.append(ip)
|
||||
|
||||
while addr:
|
||||
name = addr[0].ifa_name.decode(encoding='UTF-8')
|
||||
ip_addr = shared.sockaddr_to_ip(addr[0].ifa_addr)
|
||||
if ip_addr:
|
||||
if addr[0].ifa_netmask and not addr[0].ifa_netmask[0].sa_familiy:
|
||||
addr[0].ifa_netmask[0].sa_familiy = addr[0].ifa_addr[0].sa_familiy
|
||||
netmask = shared.sockaddr_to_ip(addr[0].ifa_netmask)
|
||||
if isinstance(netmask, tuple):
|
||||
netmaskStr = str(netmask[0])
|
||||
prefixlen = shared.ipv6_prefixlength(ipaddress.IPv6Address(netmaskStr))
|
||||
else:
|
||||
assert netmask is not None, f'sockaddr_to_ip({addr[0].ifa_netmask}) returned None'
|
||||
netmaskStr = str('0.0.0.0/' + netmask)
|
||||
prefixlen = ipaddress.IPv4Network(netmaskStr).prefixlen
|
||||
ip = shared.IP(ip_addr, prefixlen, name)
|
||||
add_ip(name, ip)
|
||||
else:
|
||||
if include_unconfigured:
|
||||
add_ip(name, None)
|
||||
addr = addr[0].ifa_next
|
||||
|
||||
libc.freeifaddrs(addr0)
|
||||
|
||||
return ips.values()
|
||||
Vendored
+198
@@ -0,0 +1,198 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import ctypes
|
||||
import socket
|
||||
import ipaddress
|
||||
import platform
|
||||
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
class Adapter(object):
|
||||
"""
|
||||
Represents a network interface device controller (NIC), such as a
|
||||
network card. An adapter can have multiple IPs.
|
||||
|
||||
On Linux aliasing (multiple IPs per physical NIC) is implemented
|
||||
by creating 'virtual' adapters, each represented by an instance
|
||||
of this class. Each of those 'virtual' adapters can have both
|
||||
a IPv4 and an IPv6 IP address.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, nice_name: str, ips: List['IP'], index: Optional[int] = None) -> None:
|
||||
|
||||
#: Unique name that identifies the adapter in the system.
|
||||
#: On Linux this is of the form of `eth0` or `eth0:1`, on
|
||||
#: Windows it is a UUID in string representation, such as
|
||||
#: `{846EE342-7039-11DE-9D20-806E6F6E6963}`.
|
||||
self.name = name
|
||||
|
||||
#: Human readable name of the adpater. On Linux this
|
||||
#: is currently the same as :attr:`name`. On Windows
|
||||
#: this is the name of the device.
|
||||
self.nice_name = nice_name
|
||||
|
||||
#: List of :class:`ifaddr.IP` instances in the order they were
|
||||
#: reported by the system.
|
||||
self.ips = ips
|
||||
|
||||
#: Adapter index as used by some API (e.g. IPv6 multicast group join).
|
||||
self.index = index
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Adapter(name={name}, nice_name={nice_name}, ips={ips}, index={index})".format(
|
||||
name=repr(self.name), nice_name=repr(self.nice_name), ips=repr(self.ips), index=repr(self.index)
|
||||
)
|
||||
|
||||
|
||||
# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format)
|
||||
_IPv4Address = str
|
||||
|
||||
# Type of an IPv6 address (a three-tuple `(ip, flowinfo, scope_id)`)
|
||||
_IPv6Address = Tuple[str, int, int]
|
||||
|
||||
|
||||
class IP(object):
|
||||
"""
|
||||
Represents an IP address of an adapter.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: Union[_IPv4Address, _IPv6Address], network_prefix: int, nice_name: str) -> None:
|
||||
|
||||
#: IP address. For IPv4 addresses this is a string in
|
||||
#: "xxx.xxx.xxx.xxx" format. For IPv6 addresses this
|
||||
#: is a three-tuple `(ip, flowinfo, scope_id)`, where
|
||||
#: `ip` is a string in the usual collon separated
|
||||
#: hex format.
|
||||
self.ip = ip
|
||||
|
||||
#: Number of bits of the IP that represent the
|
||||
#: network. For a `255.255.255.0` netmask, this
|
||||
#: number would be `24`.
|
||||
self.network_prefix = network_prefix
|
||||
|
||||
#: Human readable name for this IP.
|
||||
#: On Linux is this currently the same as the adapter name.
|
||||
#: On Windows this is the name of the network connection
|
||||
#: as configured in the system control panel.
|
||||
self.nice_name = nice_name
|
||||
|
||||
@property
|
||||
def is_IPv4(self) -> bool:
|
||||
"""
|
||||
Returns `True` if this IP is an IPv4 address and `False`
|
||||
if it is an IPv6 address.
|
||||
"""
|
||||
return not isinstance(self.ip, tuple)
|
||||
|
||||
@property
|
||||
def is_IPv6(self) -> bool:
|
||||
"""
|
||||
Returns `True` if this IP is an IPv6 address and `False`
|
||||
if it is an IPv4 address.
|
||||
"""
|
||||
return isinstance(self.ip, tuple)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "IP(ip={ip}, network_prefix={network_prefix}, nice_name={nice_name})".format(
|
||||
ip=repr(self.ip), network_prefix=repr(self.network_prefix), nice_name=repr(self.nice_name)
|
||||
)
|
||||
|
||||
|
||||
if platform.system() == "Darwin" or "BSD" in platform.system():
|
||||
|
||||
# BSD derived systems use marginally different structures
|
||||
# than either Linux or Windows.
|
||||
# I still keep it in `shared` since we can use
|
||||
# both structures equally.
|
||||
|
||||
class sockaddr(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sa_data', ctypes.c_uint8 * 14),
|
||||
]
|
||||
|
||||
class sockaddr_in(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_uint8 * 4),
|
||||
('sin_zero', ctypes.c_uint8 * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_uint8 * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
else:
|
||||
|
||||
class sockaddr(ctypes.Structure): # type: ignore
|
||||
_fields_ = [('sa_familiy', ctypes.c_uint16), ('sa_data', ctypes.c_uint8 * 14)]
|
||||
|
||||
class sockaddr_in(ctypes.Structure): # type: ignore
|
||||
_fields_ = [
|
||||
('sin_familiy', ctypes.c_uint16),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_uint8 * 4),
|
||||
('sin_zero', ctypes.c_uint8 * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure): # type: ignore
|
||||
_fields_ = [
|
||||
('sin6_familiy', ctypes.c_uint16),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_uint8 * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
|
||||
def sockaddr_to_ip(sockaddr_ptr: 'ctypes.pointer[sockaddr]') -> Optional[Union[_IPv4Address, _IPv6Address]]:
|
||||
if sockaddr_ptr:
|
||||
if sockaddr_ptr[0].sa_familiy == socket.AF_INET:
|
||||
ipv4 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in))
|
||||
ippacked = bytes(bytearray(ipv4[0].sin_addr))
|
||||
ip = str(ipaddress.ip_address(ippacked))
|
||||
return ip
|
||||
elif sockaddr_ptr[0].sa_familiy == socket.AF_INET6:
|
||||
ipv6 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in6))
|
||||
flowinfo = ipv6[0].sin6_flowinfo
|
||||
ippacked = bytes(bytearray(ipv6[0].sin6_addr))
|
||||
ip = str(ipaddress.ip_address(ippacked))
|
||||
scope_id = ipv6[0].sin6_scope_id
|
||||
return (ip, flowinfo, scope_id)
|
||||
return None
|
||||
|
||||
|
||||
def ipv6_prefixlength(address: ipaddress.IPv6Address) -> int:
|
||||
prefix_length = 0
|
||||
for i in range(address.max_prefixlen):
|
||||
if int(address) >> i & 1:
|
||||
prefix_length = prefix_length + 1
|
||||
return prefix_length
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from typing import Iterable, List
|
||||
|
||||
import RNS.vendor.ifaddr._shared as shared
|
||||
|
||||
NO_ERROR = 0
|
||||
ERROR_BUFFER_OVERFLOW = 111
|
||||
MAX_ADAPTER_NAME_LENGTH = 256
|
||||
MAX_ADAPTER_DESCRIPTION_LENGTH = 128
|
||||
MAX_ADAPTER_ADDRESS_LENGTH = 8
|
||||
AF_UNSPEC = 0
|
||||
|
||||
|
||||
class SOCKET_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [('lpSockaddr', ctypes.POINTER(shared.sockaddr)), ('iSockaddrLength', wintypes.INT)]
|
||||
|
||||
|
||||
class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
IP_ADAPTER_UNICAST_ADDRESS._fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('Flags', wintypes.DWORD),
|
||||
('Next', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('Address', SOCKET_ADDRESS),
|
||||
('PrefixOrigin', ctypes.c_uint),
|
||||
('SuffixOrigin', ctypes.c_uint),
|
||||
('DadState', ctypes.c_uint),
|
||||
('ValidLifetime', wintypes.ULONG),
|
||||
('PreferredLifetime', wintypes.ULONG),
|
||||
('LeaseLifetime', wintypes.ULONG),
|
||||
('OnLinkPrefixLength', ctypes.c_uint8),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
IP_ADAPTER_ADDRESSES._fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('IfIndex', wintypes.DWORD),
|
||||
('Next', ctypes.POINTER(IP_ADAPTER_ADDRESSES)),
|
||||
('AdapterName', ctypes.c_char_p),
|
||||
('FirstUnicastAddress', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('FirstAnycastAddress', ctypes.c_void_p),
|
||||
('FirstMulticastAddress', ctypes.c_void_p),
|
||||
('FirstDnsServerAddress', ctypes.c_void_p),
|
||||
('DnsSuffix', ctypes.c_wchar_p),
|
||||
('Description', ctypes.c_wchar_p),
|
||||
('FriendlyName', ctypes.c_wchar_p),
|
||||
]
|
||||
|
||||
|
||||
iphlpapi = ctypes.windll.LoadLibrary("Iphlpapi") # type: ignore
|
||||
|
||||
|
||||
def enumerate_interfaces_of_adapter(
|
||||
nice_name: str, address: IP_ADAPTER_UNICAST_ADDRESS
|
||||
) -> Iterable[shared.IP]:
|
||||
|
||||
# Iterate through linked list and fill list
|
||||
addresses = [] # type: List[IP_ADAPTER_UNICAST_ADDRESS]
|
||||
while True:
|
||||
addresses.append(address)
|
||||
if not address.Next:
|
||||
break
|
||||
address = address.Next[0]
|
||||
|
||||
for address in addresses:
|
||||
ip = shared.sockaddr_to_ip(address.Address.lpSockaddr)
|
||||
assert ip is not None, f'sockaddr_to_ip({address.Address.lpSockaddr}) returned None'
|
||||
network_prefix = address.OnLinkPrefixLength
|
||||
yield shared.IP(ip, network_prefix, nice_name)
|
||||
|
||||
|
||||
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
|
||||
|
||||
# Call GetAdaptersAddresses() with error and buffer size handling
|
||||
|
||||
addressbuffersize = wintypes.ULONG(15 * 1024)
|
||||
retval = ERROR_BUFFER_OVERFLOW
|
||||
while retval == ERROR_BUFFER_OVERFLOW:
|
||||
addressbuffer = ctypes.create_string_buffer(addressbuffersize.value)
|
||||
retval = iphlpapi.GetAdaptersAddresses(
|
||||
wintypes.ULONG(AF_UNSPEC),
|
||||
wintypes.ULONG(0),
|
||||
None,
|
||||
ctypes.byref(addressbuffer),
|
||||
ctypes.byref(addressbuffersize),
|
||||
)
|
||||
if retval != NO_ERROR:
|
||||
raise ctypes.WinError() # type: ignore
|
||||
|
||||
# Iterate through adapters fill array
|
||||
address_infos = [] # type: List[IP_ADAPTER_ADDRESSES]
|
||||
address_info = IP_ADAPTER_ADDRESSES.from_buffer(addressbuffer)
|
||||
while True:
|
||||
address_infos.append(address_info)
|
||||
if not address_info.Next:
|
||||
break
|
||||
address_info = address_info.Next[0]
|
||||
|
||||
# Iterate through unicast addresses
|
||||
result = [] # type: List[shared.Adapter]
|
||||
for adapter_info in address_infos:
|
||||
|
||||
# We don't expect non-ascii characters here, so encoding shouldn't matter
|
||||
name = adapter_info.AdapterName.decode()
|
||||
nice_name = adapter_info.Description
|
||||
index = adapter_info.IfIndex
|
||||
|
||||
if adapter_info.FirstUnicastAddress:
|
||||
ips = enumerate_interfaces_of_adapter(
|
||||
adapter_info.FriendlyName, adapter_info.FirstUnicastAddress[0]
|
||||
)
|
||||
ips = list(ips)
|
||||
result.append(shared.Adapter(name, nice_name, ips, index=index))
|
||||
elif include_unconfigured:
|
||||
result.append(shared.Adapter(name, nice_name, [], index=index))
|
||||
|
||||
return result
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
import ipaddress
|
||||
import RNS.vendor.ifaddr
|
||||
import socket
|
||||
|
||||
from typing import List
|
||||
|
||||
AF_INET6 = socket.AF_INET6.value
|
||||
AF_INET = socket.AF_INET.value
|
||||
|
||||
def interfaces() -> List[str]:
|
||||
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
|
||||
return [a.name for a in adapters]
|
||||
|
||||
def ifaddresses(ifname) -> dict:
|
||||
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
|
||||
ifa = {}
|
||||
for a in adapters:
|
||||
if a.name == ifname:
|
||||
ipv4s = []
|
||||
ipv6s = []
|
||||
for ip in a.ips:
|
||||
t = {}
|
||||
if ip.is_IPv4:
|
||||
net = ipaddress.ip_network(str(ip.ip)+"/"+str(ip.network_prefix), strict=False)
|
||||
t["addr"] = ip.ip
|
||||
t["prefix"] = ip.network_prefix
|
||||
t["broadcast"] = str(net.broadcast_address)
|
||||
ipv4s.append(t)
|
||||
if ip.is_IPv6:
|
||||
t["addr"] = ip.ip[0]
|
||||
ipv6s.append(t)
|
||||
|
||||
if len(ipv4s) > 0:
|
||||
ifa[AF_INET] = ipv4s
|
||||
if len(ipv6s) > 0:
|
||||
ifa[AF_INET6] = ipv6s
|
||||
|
||||
return ifa
|
||||
Vendored
@@ -38,7 +38,6 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
||||
- Update NomadNet screenshots
|
||||
- Update Sideband screenshots
|
||||
- Installation
|
||||
- Install docs for fedora, needs `python3-netifaces`
|
||||
- Add a *Reticulum On Raspberry Pi* section
|
||||
- Update *Reticulum On Android* section if necessary
|
||||
- Update Android install documentation.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: d8447d44585d036754ef13c1e3b2bcb2
|
||||
config: c5670e7931d15fbef274e2eee17c3561
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -10,22 +10,45 @@ scenarios.
|
||||
Standalone Reticulum Installation
|
||||
=============================================
|
||||
If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via ``pip``:
|
||||
the easiest way is via the ``pip`` package manager:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
If you no not already have pip installed, you can install it using the package manager
|
||||
If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like ``sudo apt install python3-pip``,
|
||||
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using ``pip``:
|
||||
``sudo pamac install python-pip`` or similar.
|
||||
|
||||
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install ./rns-0.4.6-py3-none-any.whl
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
|
||||
.. code::
|
||||
|
||||
# Debian / Ubuntu / Derivatives
|
||||
sudo apt install build-essential
|
||||
|
||||
# Arch / Manjaro / Derivatives
|
||||
sudo pamac install base-devel
|
||||
|
||||
# Fedora
|
||||
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||
|
||||
With the base development packages installed, ``pip`` should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don't have pre-
|
||||
compiled packages available.
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
|
||||
@@ -203,12 +226,12 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
|
||||
target_host = dublin.connect.reticulum.network
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt hub
|
||||
[[RNS Testnet Frankfurt]]
|
||||
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
|
||||
[[RNS Testnet BetweenTheBorders]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
target_port = 5377
|
||||
target_host = betweentheborders.com
|
||||
target_port = 4242
|
||||
|
||||
# Interface to I2P hub A
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
@@ -268,7 +291,7 @@ For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
pip3 install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
@@ -283,7 +306,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial netifaces
|
||||
pip3 install cryptography pyserial
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
@@ -320,30 +343,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Raspberry Pi
|
||||
==============================================
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Android
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
|
||||
|
||||
@@ -409,6 +416,74 @@ It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
|
||||
|
||||
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Debian released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Ubuntu released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
******************************
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
Table Of Contents
|
||||
=================
|
||||
|
||||
@@ -223,7 +223,7 @@ interfaces, similar to the ``ifconfig`` program.
|
||||
Traffic : 63.23 KB↑
|
||||
80.17 KB↓
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
|
||||
Status : Up
|
||||
Mode : Full
|
||||
Rate : 10.00 Mbps
|
||||
@@ -266,7 +266,7 @@ destinations on the Reticulum network.
|
||||
rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
|
||||
# Example output
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
|
||||
|
||||
.. code:: text
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.5.0 beta',
|
||||
VERSION: '0.5.2 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -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.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
@@ -229,18 +229,38 @@ scenarios.</p>
|
||||
<section id="standalone-reticulum-installation">
|
||||
<h2>Standalone Reticulum Installation<a class="headerlink" href="#standalone-reticulum-installation" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
the easiest way is via the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you no not already have pip installed, you can install it using the package manager
|
||||
<p>If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">apt</span> <span class="pre">install</span> <span class="pre">python3-pip</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.4.6</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar.</p>
|
||||
<p>You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.5.1</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="resolving-dependency-installation-issues">
|
||||
<h2>Resolving Dependency & Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h2>
|
||||
<p>On some platforms, there may not be binary packages available for all dependencies, and
|
||||
<code class="docutils literal notranslate"><span class="pre">pip</span></code> installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Debian / Ubuntu / Derivatives</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span>
|
||||
|
||||
<span class="c1"># Arch / Manjaro / Derivatives</span>
|
||||
<span class="n">sudo</span> <span class="n">pamac</span> <span class="n">install</span> <span class="n">base</span><span class="o">-</span><span class="n">devel</span>
|
||||
|
||||
<span class="c1"># Fedora</span>
|
||||
<span class="n">sudo</span> <span class="n">dnf</span> <span class="n">groupinstall</span> <span class="s2">"Development Tools"</span> <span class="s2">"Development Libraries"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>With the base development packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don’t have pre-
|
||||
compiled packages available.</p>
|
||||
</section>
|
||||
<section id="try-using-a-reticulum-based-program">
|
||||
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to try using a program built with Reticulum, a few different
|
||||
@@ -374,12 +394,12 @@ by adding one of the following interfaces to your <code class="docutils literal
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">dublin</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4965</span>
|
||||
|
||||
<span class="c1"># TCP/IP interface to the Frankfurt hub</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Frankfurt</span><span class="p">]]</span>
|
||||
<span class="c1"># TCP/IP interface to the BetweenTheBorders Hub (community-provided)</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">BetweenTheBorders</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">frankfurt</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">5377</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">betweentheborders</span><span class="o">.</span><span class="n">com</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
|
||||
<span class="c1"># Interface to I2P hub A</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">I2P</span> <span class="n">Hub</span> <span class="n">A</span><span class="p">]]</span>
|
||||
@@ -430,7 +450,7 @@ started is to install the latest release of Reticulum via pip:</p>
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some <a class="reference internal" href="examples.html#examples-main"><span class="std std-ref">Example Programs</span></a>.</p>
|
||||
<p>For extended functionality, you can install optional dependencies:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</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>
|
||||
@@ -441,7 +461,7 @@ likely be to look at some <a class="reference internal" href="examples.html#exam
|
||||
utilities, you’ll want to get the latest source from GitHub. In that case,
|
||||
don’t use pip, but try this recipe:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span>
|
||||
|
||||
<span class="c1"># Clone repository</span>
|
||||
<span class="n">git</span> <span class="n">clone</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">markqvist</span><span class="o">/</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">git</span>
|
||||
@@ -478,28 +498,12 @@ your first pull request, it is probably a good idea to introduce yourself on
|
||||
the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">disucssion forum on GitHub</a>,
|
||||
or ask one of the developers or maintainers for a good place to start.</p>
|
||||
</section>
|
||||
<section id="reticulum-on-arm64">
|
||||
<h2>Reticulum on ARM64<a class="headerlink" href="#reticulum-on-arm64" title="Permalink to this heading">#</a></h2>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="reticulum-on-raspberry-pi">
|
||||
<h2>Reticulum on Raspberry Pi<a class="headerlink" href="#reticulum-on-raspberry-pi" title="Permalink to this heading">#</a></h2>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
</section>
|
||||
<section id="reticulum-on-android">
|
||||
<h2>Reticulum on Android<a class="headerlink" href="#reticulum-on-android" title="Permalink to this heading">#</a></h2>
|
||||
<section id="platform-specific-install-notes">
|
||||
<h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
|
||||
<p>Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.</p>
|
||||
<section id="android">
|
||||
<h3>Android<a class="headerlink" href="#android" title="Permalink to this heading">#</a></h3>
|
||||
<p>Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>.</p>
|
||||
<p>For more control and features, you can use Reticulum and related programs via
|
||||
@@ -557,6 +561,66 @@ locally on your device using the following command:</p>
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the <a class="reference external" href="https://github.com/markqvist/sideband">Sideband source code</a> as an example and startig point.</p>
|
||||
</section>
|
||||
<section id="arm64">
|
||||
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="raspberry-pi">
|
||||
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.</p>
|
||||
</section>
|
||||
<section id="debian-bookworm">
|
||||
<h3>Debian Bookworm<a class="headerlink" href="#debian-bookworm" title="Permalink to this heading">#</a></h3>
|
||||
<p>On versions of Debian released after April 2023, it is no longer possible
|
||||
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
|
||||
|
||||
<span class="c1"># Make installed programs available on the command line</span>
|
||||
<span class="n">pipx</span> <span class="n">ensurepath</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="ubuntu-lunar">
|
||||
<h3>Ubuntu Lunar<a class="headerlink" href="#ubuntu-lunar" title="Permalink to this heading">#</a></h3>
|
||||
<p>On versions of Ubuntu released after April 2023, it is no longer possible
|
||||
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
|
||||
|
||||
<span class="c1"># Make installed programs available on the command line</span>
|
||||
<span class="n">pipx</span> <span class="n">ensurepath</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="pure-python-reticulum">
|
||||
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this heading">#</a></h2>
|
||||
<p>In some rare cases, and on more obscure system types, it is not possible to
|
||||
@@ -639,6 +703,7 @@ section of this manual.</p>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
|
||||
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li><a class="reference internal" href="#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
|
||||
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
|
||||
@@ -652,9 +717,14 @@ section of this manual.</p>
|
||||
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-raspberry-pi">Reticulum on Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-android">Reticulum on Android</a></li>
|
||||
<li><a class="reference internal" href="#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li><a class="reference internal" href="#android">Android</a></li>
|
||||
<li><a class="reference internal" href="#arm64">ARM64</a></li>
|
||||
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li><a class="reference internal" href="#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Supported Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
+12
-6
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
@@ -242,6 +242,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
|
||||
@@ -255,9 +256,14 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-raspberry-pi">Reticulum on Raspberry Pi</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-android">Reticulum on Android</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#android">Android</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#arm64">ARM64</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Supported Interfaces" href="interfaces.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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.
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>API Reference - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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><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><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
|
||||
|
||||
@@ -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.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.5.0 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
@@ -420,7 +420,7 @@ AutoInterface[Local]
|
||||
Traffic : 63.23 KB↑
|
||||
80.17 KB↓
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
|
||||
Status : Up
|
||||
Mode : Full
|
||||
Rate : 10.00 Mbps
|
||||
@@ -459,7 +459,7 @@ destinations on the Reticulum network.</p>
|
||||
rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
|
||||
# Example output
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.5.0 beta documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.5.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=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<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.5.0 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.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.5.0 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.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">
|
||||
|
||||
@@ -13,6 +13,9 @@ project = 'Reticulum Network Stack'
|
||||
copyright = '2023, Mark Qvist'
|
||||
author = 'Mark Qvist'
|
||||
|
||||
exec(open("../../RNS/_version.py", "r").read())
|
||||
version = __version__
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
import RNS
|
||||
release = RNS._version.__version__+" beta"
|
||||
|
||||
@@ -10,22 +10,45 @@ scenarios.
|
||||
Standalone Reticulum Installation
|
||||
=============================================
|
||||
If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via ``pip``:
|
||||
the easiest way is via the ``pip`` package manager:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
If you no not already have pip installed, you can install it using the package manager
|
||||
If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like ``sudo apt install python3-pip``,
|
||||
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using ``pip``:
|
||||
``sudo pamac install python-pip`` or similar.
|
||||
|
||||
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install ./rns-0.4.6-py3-none-any.whl
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
|
||||
.. code::
|
||||
|
||||
# Debian / Ubuntu / Derivatives
|
||||
sudo apt install build-essential
|
||||
|
||||
# Arch / Manjaro / Derivatives
|
||||
sudo pamac install base-devel
|
||||
|
||||
# Fedora
|
||||
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||
|
||||
With the base development packages installed, ``pip`` should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don't have pre-
|
||||
compiled packages available.
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
|
||||
@@ -203,12 +226,12 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
|
||||
target_host = dublin.connect.reticulum.network
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt hub
|
||||
[[RNS Testnet Frankfurt]]
|
||||
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
|
||||
[[RNS Testnet BetweenTheBorders]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
target_port = 5377
|
||||
target_host = betweentheborders.com
|
||||
target_port = 4242
|
||||
|
||||
# Interface to I2P hub A
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
@@ -268,7 +291,7 @@ For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
pip3 install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
@@ -283,7 +306,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial netifaces
|
||||
pip3 install cryptography pyserial
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
@@ -320,30 +343,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Raspberry Pi
|
||||
==============================================
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Android
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
|
||||
|
||||
@@ -409,6 +416,74 @@ It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
|
||||
|
||||
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Debian released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Ubuntu released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
******************************
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
Table Of Contents
|
||||
=================
|
||||
|
||||
@@ -223,7 +223,7 @@ interfaces, similar to the ``ifconfig`` program.
|
||||
Traffic : 63.23 KB↑
|
||||
80.17 KB↓
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
|
||||
Status : Up
|
||||
Mode : Full
|
||||
Rate : 10.00 Mbps
|
||||
@@ -266,7 +266,7 @@ destinations on the Reticulum network.
|
||||
rnpath c89b4da064bf66d280f0e4d8abfd9806
|
||||
|
||||
# Example output
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
|
||||
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
|
||||
|
||||
.. code:: text
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ if pure_python:
|
||||
long_description = long_description.replace("</p>", "</p>"+pure_notice)
|
||||
else:
|
||||
pkg_name = "rns"
|
||||
requirements = ['cryptography>=3.4.7', 'pyserial>=3.5', 'netifaces']
|
||||
requirements = ['cryptography>=3.4.7', 'pyserial>=3.5']
|
||||
|
||||
excluded_modules = exclude=["tests.*", "tests"]
|
||||
packages = setuptools.find_packages(exclude=excluded_modules)
|
||||
@@ -53,5 +53,5 @@ setuptools.setup(
|
||||
]
|
||||
},
|
||||
install_requires=requirements,
|
||||
python_requires='>=3.6',
|
||||
python_requires='>=3.7',
|
||||
)
|
||||
|
||||
+79
-74
@@ -291,6 +291,8 @@ class TestChannel(unittest.TestCase):
|
||||
raw = envelope.pack()
|
||||
self.h.channel._receive(raw)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(1, handler1_called)
|
||||
self.assertEqual(0, handler2_called)
|
||||
|
||||
@@ -299,6 +301,8 @@ class TestChannel(unittest.TestCase):
|
||||
raw = envelope.pack()
|
||||
self.h.channel._receive(raw)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(2, handler1_called)
|
||||
self.assertEqual(1, handler2_called)
|
||||
|
||||
@@ -357,6 +361,8 @@ class TestChannel(unittest.TestCase):
|
||||
|
||||
self.h.channel._receive(packet.raw)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(1, len(decoded))
|
||||
|
||||
rx_message = decoded[0]
|
||||
@@ -388,6 +394,7 @@ class TestChannel(unittest.TestCase):
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
self.h.channel._receive(packet.raw)
|
||||
time.sleep(0.2)
|
||||
result = buffer.readline()
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
@@ -397,106 +404,104 @@ class TestChannel(unittest.TestCase):
|
||||
|
||||
self.assertEqual(data, decoded)
|
||||
|
||||
def test_buffer_big(self):
|
||||
writer = RNS.Buffer.create_writer(15, self.h.channel)
|
||||
reader = RNS.Buffer.create_reader(15, self.h.channel)
|
||||
data = "01234556789"*1024 # 10 KB
|
||||
count = 0
|
||||
write_finished = False
|
||||
# def test_buffer_big(self):
|
||||
# writer = RNS.Buffer.create_writer(15, self.h.channel)
|
||||
# reader = RNS.Buffer.create_reader(15, self.h.channel)
|
||||
# data = "01234556789"*1024 # 10 KB
|
||||
# count = 0
|
||||
# write_finished = False
|
||||
|
||||
def write_thread():
|
||||
nonlocal count, write_finished
|
||||
count = writer.write(data.encode("utf-8"))
|
||||
writer.flush()
|
||||
writer.close()
|
||||
write_finished = True
|
||||
threading.Thread(target=write_thread, name="Write Thread", daemon=True).start()
|
||||
# def write_thread():
|
||||
# nonlocal count, write_finished
|
||||
# count = writer.write(data.encode("utf-8"))
|
||||
# writer.flush()
|
||||
# writer.close()
|
||||
# write_finished = True
|
||||
# threading.Thread(target=write_thread, name="Write Thread", daemon=True).start()
|
||||
|
||||
while not write_finished or next(filter(lambda x: x.state != MessageState.MSGSTATE_DELIVERED,
|
||||
self.h.outlet.packets), None) is not None:
|
||||
with self.h.outlet.lock:
|
||||
for packet in self.h.outlet.packets:
|
||||
if packet.state != MessageState.MSGSTATE_DELIVERED:
|
||||
self.h.channel._receive(packet.raw)
|
||||
packet.delivered()
|
||||
time.sleep(0.0001)
|
||||
# while not write_finished or next(filter(lambda x: x.state != MessageState.MSGSTATE_DELIVERED,
|
||||
# self.h.outlet.packets), None) is not None:
|
||||
# with self.h.outlet.lock:
|
||||
# for packet in self.h.outlet.packets:
|
||||
# if packet.state != MessageState.MSGSTATE_DELIVERED:
|
||||
# self.h.channel._receive(packet.raw)
|
||||
# packet.delivered()
|
||||
# time.sleep(0.0001)
|
||||
|
||||
self.assertEqual(len(data), count)
|
||||
# self.assertEqual(len(data), count)
|
||||
|
||||
read_finished = False
|
||||
result = bytes()
|
||||
# read_finished = False
|
||||
# result = bytes()
|
||||
|
||||
def read_thread():
|
||||
nonlocal read_finished, result
|
||||
result = reader.read()
|
||||
read_finished = True
|
||||
threading.Thread(target=read_thread, name="Read Thread", daemon=True).start()
|
||||
# def read_thread():
|
||||
# nonlocal read_finished, result
|
||||
# result = reader.read()
|
||||
# read_finished = True
|
||||
# threading.Thread(target=read_thread, name="Read Thread", daemon=True).start()
|
||||
|
||||
timeout_at = time.time() + 7
|
||||
while not read_finished and time.time() < timeout_at:
|
||||
time.sleep(0.001)
|
||||
# timeout_at = time.time() + 7
|
||||
# while not read_finished and time.time() < timeout_at:
|
||||
# time.sleep(0.001)
|
||||
|
||||
self.assertTrue(read_finished)
|
||||
self.assertEqual(len(data), len(result))
|
||||
# self.assertTrue(read_finished)
|
||||
# self.assertEqual(len(data), len(result))
|
||||
|
||||
decoded = result.decode("utf-8")
|
||||
# decoded = result.decode("utf-8")
|
||||
|
||||
self.assertSequenceEqual(data, decoded)
|
||||
# self.assertSequenceEqual(data, decoded)
|
||||
|
||||
def test_buffer_small_with_callback(self):
|
||||
callbacks = 0
|
||||
last_cb_value = None
|
||||
# def test_buffer_small_with_callback(self):
|
||||
# callbacks = 0
|
||||
# last_cb_value = None
|
||||
|
||||
def callback(ready: int):
|
||||
nonlocal callbacks, last_cb_value
|
||||
callbacks += 1
|
||||
last_cb_value = ready
|
||||
# def callback(ready: int):
|
||||
# nonlocal callbacks, last_cb_value
|
||||
# callbacks += 1
|
||||
# last_cb_value = ready
|
||||
|
||||
data = "Hello\n"
|
||||
with RNS.RawChannelWriter(0, self.h.channel) as writer, RNS.RawChannelReader(0, self.h.channel) as reader:
|
||||
reader.add_ready_callback(callback)
|
||||
count = writer.write(data.encode("utf-8"))
|
||||
writer.flush()
|
||||
# data = "Hello\n"
|
||||
# with RNS.RawChannelWriter(0, self.h.channel) as writer, RNS.RawChannelReader(0, self.h.channel) as reader:
|
||||
# reader.add_ready_callback(callback)
|
||||
# count = writer.write(data.encode("utf-8"))
|
||||
# writer.flush()
|
||||
|
||||
self.assertEqual(len(data), count)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
# self.assertEqual(len(data), count)
|
||||
# self.assertEqual(1, len(self.h.outlet.packets))
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
self.h.channel._receive(packet.raw)
|
||||
packet.delivered()
|
||||
# packet = self.h.outlet.packets[0]
|
||||
# self.h.channel._receive(packet.raw)
|
||||
# packet.delivered()
|
||||
|
||||
self.assertEqual(1, callbacks)
|
||||
self.assertEqual(len(data), last_cb_value)
|
||||
# self.assertEqual(1, callbacks)
|
||||
# self.assertEqual(len(data), last_cb_value)
|
||||
|
||||
result = reader.readline()
|
||||
# result = reader.readline()
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(len(result), len(data))
|
||||
# self.assertIsNotNone(result)
|
||||
# self.assertEqual(len(result), len(data))
|
||||
|
||||
decoded = result.decode("utf-8")
|
||||
# decoded = result.decode("utf-8")
|
||||
|
||||
self.assertEqual(data, decoded)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
# self.assertEqual(data, decoded)
|
||||
# self.assertEqual(1, len(self.h.outlet.packets))
|
||||
|
||||
result = reader.read(1)
|
||||
# result = reader.read(1)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertTrue(self.h.channel.is_ready_to_send())
|
||||
# self.assertIsNone(result)
|
||||
# self.assertTrue(self.h.channel.is_ready_to_send())
|
||||
|
||||
writer.close()
|
||||
# writer.close()
|
||||
|
||||
self.assertEqual(2, len(self.h.outlet.packets))
|
||||
# self.assertEqual(2, len(self.h.outlet.packets))
|
||||
|
||||
packet = self.h.outlet.packets[1]
|
||||
self.h.channel._receive(packet.raw)
|
||||
packet.delivered()
|
||||
|
||||
result = reader.read(1)
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertTrue(len(result) == 0)
|
||||
# packet = self.h.outlet.packets[1]
|
||||
# self.h.channel._receive(packet.raw)
|
||||
# packet.delivered()
|
||||
|
||||
# result = reader.read(1)
|
||||
|
||||
# self.assertIsNotNone(result)
|
||||
# self.assertTrue(len(result) == 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+56
-28
@@ -61,6 +61,7 @@ class TestLink(unittest.TestCase):
|
||||
def tearDownClass(cls):
|
||||
close_rns()
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_0_valid_announce(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -71,6 +72,7 @@ class TestLink(unittest.TestCase):
|
||||
ap.pack()
|
||||
self.assertEqual(RNS.Identity.validate_announce(ap), True)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_1_invalid_announce(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -86,6 +88,7 @@ class TestLink(unittest.TestCase):
|
||||
ap.send()
|
||||
self.assertEqual(RNS.Identity.validate_announce(ap), False)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_2_establish(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -105,6 +108,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_3_packets(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -171,6 +175,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_4_micro_resource(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -206,6 +211,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_5_mini_resource(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -241,6 +247,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_6_small_resource(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
@@ -276,6 +283,7 @@ class TestLink(unittest.TestCase):
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_7_medium_resource(self):
|
||||
if RNS.Cryptography.backend() == "internal":
|
||||
print("Skipping medium resource test...")
|
||||
@@ -314,6 +322,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_9_large_resource(self):
|
||||
if RNS.Cryptography.backend() == "internal":
|
||||
print("Skipping large resource test...")
|
||||
@@ -352,6 +361,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
#@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_10_channel_round_trip(self):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
@@ -393,13 +403,14 @@ class TestLink(unittest.TestCase):
|
||||
self.assertEqual("Hello back", rx_message.data)
|
||||
self.assertEqual(test_message.id, rx_message.id)
|
||||
self.assertNotEqual(test_message.not_serialized, rx_message.not_serialized)
|
||||
self.assertEqual(1, len(l1._channel._rx_ring))
|
||||
self.assertEqual(0, len(l1._channel._rx_ring))
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
self.assertEqual(0, len(l1._channel._rx_ring))
|
||||
|
||||
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_11_buffer_round_trip(self):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
@@ -442,6 +453,7 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None and os.getenv('RUN_SLOW_TESTS') == None, "Skipping")
|
||||
def test_12_buffer_round_trip_big(self, local_bitrate = None):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
@@ -479,6 +491,8 @@ class TestLink(unittest.TestCase):
|
||||
buffer = None
|
||||
received = []
|
||||
def handle_data(ready_bytes: int):
|
||||
# TODO: Remove
|
||||
RNS.log("Handling data")
|
||||
data = buffer.read(ready_bytes)
|
||||
received.append(data)
|
||||
|
||||
@@ -487,48 +501,60 @@ class TestLink(unittest.TestCase):
|
||||
|
||||
# try to make the message big enough to split across packets, but
|
||||
# small enough to make the test complete in a reasonable amount of time
|
||||
seed_text = "0123456789"
|
||||
message = seed_text*ceil(min(max(local_interface.bitrate / 8,
|
||||
StreamDataMessage.MAX_DATA_LEN * 2 / len(seed_text)),
|
||||
1000))
|
||||
# seed_text = "0123456789"
|
||||
# message = seed_text*ceil(min(max(local_interface.bitrate / 8,
|
||||
# StreamDataMessage.MAX_DATA_LEN * 2 / len(seed_text)),
|
||||
# 1000))
|
||||
|
||||
if local_interface.bitrate < 1000:
|
||||
target_bytes = 3000
|
||||
else:
|
||||
target_bytes = 16000
|
||||
|
||||
|
||||
message = os.urandom(target_bytes)
|
||||
|
||||
# the return message will have an appendage string " back at you"
|
||||
# for every StreamDataMessage that arrives. To verify, we need
|
||||
# to insert that string every MAX_DATA_LEN and also at the end.
|
||||
expected_rx_message = ""
|
||||
expected_rx_message = b""
|
||||
for i in range(0, len(message)):
|
||||
if i > 0 and (i % StreamDataMessage.MAX_DATA_LEN) == 0:
|
||||
expected_rx_message += " back at you"
|
||||
expected_rx_message += message[i]
|
||||
expected_rx_message += " back at you"
|
||||
expected_rx_message += " back at you".encode("utf-8")
|
||||
expected_rx_message += bytes([message[i]])
|
||||
expected_rx_message += " back at you".encode("utf-8")
|
||||
|
||||
# since the segments will be received at max length for a
|
||||
# StreamDataMessage, the appended text will end up in a
|
||||
# separate packet.
|
||||
expected_chunk_count = ceil(len(message)/StreamDataMessage.MAX_DATA_LEN * 2)
|
||||
expected_chunk_count = ceil(len(message)/StreamDataMessage.MAX_DATA_LEN * 2)-1
|
||||
print("Sending " + str(len(message)) + " bytes, receiving " + str(len(expected_rx_message)) + " bytes, " +
|
||||
"expecting " + str(expected_chunk_count) + " chunks of " + str(StreamDataMessage.MAX_DATA_LEN) + " bytes")
|
||||
transfer_sleep = max(expected_chunk_count * 3 * c_rns.MTU / local_interface.bitrate * 8, 3)
|
||||
print("Will take up to " + str(round(transfer_sleep, 0)) + " seconds to transfer")
|
||||
expected_ready_time = time.time() + transfer_sleep
|
||||
buffer.write(message.encode("utf-8"))
|
||||
|
||||
buffer.write(message)
|
||||
buffer.flush()
|
||||
|
||||
# delay a reasonable time for the send and receive
|
||||
# a chunk each way plus a little more for a proof each way
|
||||
while time.time() < expected_ready_time and len(received) < expected_chunk_count:
|
||||
time.sleep(0.1)
|
||||
# sleep for at least one more chunk round trip in case there
|
||||
# are more chunks than expected
|
||||
if time.time() < expected_ready_time:
|
||||
time.sleep(max(c_rns.MTU * 2 / local_interface.bitrate * 8, 1))
|
||||
# while time.time() < expected_ready_time and len(received) < expected_chunk_count:
|
||||
# time.sleep(0.1)
|
||||
# # sleep for at least one more chunk round trip in case there
|
||||
# # are more chunks than expected
|
||||
# if time.time() < expected_ready_time:
|
||||
# time.sleep(max(c_rns.MTU * 2 / local_interface.bitrate * 8, 1))
|
||||
|
||||
# Why does this not always work out correctly?
|
||||
# self.assertEqual(expected_chunk_count, len(received))
|
||||
timeout = time.time() + 10
|
||||
while len(received) < expected_chunk_count and not time.time() > timeout:
|
||||
time.sleep(2)
|
||||
print(f"Received {len(received)} out of {expected_chunk_count} chunks so far")
|
||||
time.sleep(2)
|
||||
print(f"Received {len(received)} out of {expected_chunk_count} chunks")
|
||||
|
||||
data = bytearray()
|
||||
for rx in received:
|
||||
data.extend(rx)
|
||||
|
||||
rx_message = data.decode("utf-8")
|
||||
rx_message = data
|
||||
|
||||
self.assertEqual(len(expected_rx_message), len(rx_message))
|
||||
for i in range(0, len(expected_rx_message)):
|
||||
@@ -546,7 +572,7 @@ class TestLink(unittest.TestCase):
|
||||
# RUN_SLOW_TESTS=1 python tests/link.py TestLink.test_13_buffer_round_trip_big_slow
|
||||
# Or
|
||||
# make RUN_SLOW_TESTS=1 test
|
||||
@skipIf(int(os.getenv('RUN_SLOW_TESTS', 0)) < 1, "Not running slow tests")
|
||||
@skipIf(os.getenv('RUN_SLOW_TESTS') == None, "Not running slow tests")
|
||||
def test_13_buffer_round_trip_big_slow(self):
|
||||
self.test_12_buffer_round_trip_big(local_bitrate=410)
|
||||
|
||||
@@ -610,8 +636,10 @@ def targets(yp=False):
|
||||
channel = link.get_channel()
|
||||
|
||||
def handle_message(message):
|
||||
message.data = message.data + " back"
|
||||
channel.send(message)
|
||||
if isinstance(message, MessageTest):
|
||||
message.data = message.data + " back"
|
||||
channel.send(message)
|
||||
|
||||
channel.register_message_type(MessageTest)
|
||||
channel.add_message_handler(handle_message)
|
||||
|
||||
@@ -619,12 +647,12 @@ def targets(yp=False):
|
||||
|
||||
def handle_buffer(ready_bytes: int):
|
||||
data = buffer.read(ready_bytes)
|
||||
buffer.write((data.decode("utf-8") + " back at you").encode("utf-8"))
|
||||
buffer.write(data + " back at you".encode("utf-8"))
|
||||
buffer.flush()
|
||||
|
||||
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, handle_buffer)
|
||||
|
||||
m_rns = RNS.Reticulum("./tests/rnsconfig")
|
||||
m_rns = RNS.Reticulum("./tests/rnsconfig", logdest=RNS.LOG_FILE, loglevel=RNS.LOG_EXTREME)
|
||||
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
d1 = RNS.Destination(id1, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
|
||||
d1.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
||||
|
||||
Reference in New Issue
Block a user