Compare commits

..

29 Commits

Author SHA1 Message Date
Mark Qvist
58004d7c05 Updated documentation 2023-03-02 12:47:55 +01:00
Mark Qvist
aa0d9c5c13 Merge branch 'master' of github.com:markqvist/Reticulum 2023-03-02 12:05:06 +01:00
Mark Qvist
9e46950e28 Added output to echo example 2023-03-02 12:04:50 +01:00
markqvist
a6551fc019
Merge pull request #246 from gdt/fix-transmit-hash
AutoInterface: Drop embedded scope identifier on fe80::
2023-03-02 11:34:00 +01:00
markqvist
a06ae40797
Merge pull request #236 from faragher/master
Additional error messages for offline flashing.
2023-03-02 11:31:31 +01:00
markqvist
1db08438df
Merge pull request #248 from Erethon/hkdf-remove-dead-code
hkdf: Remove duplicate check if the salt is None
2023-03-02 11:29:18 +01:00
markqvist
89aa51ab61
Merge pull request #245 from acehoss/feature/channel
Channel: reliable delivery over Link
2023-03-02 11:27:15 +01:00
Dionysis Grigoropoulos
ddb7a92c15 hkdf: Remove duplicate check if the salt is None
The second if isn't needed since we initialize the salt with zeroes
earlier. If instead we meant to pass an empty bytes class to the HMAC
implementation, the end result would be the same, since it's gonna get
padded with zeroes in the HMAC code.
2023-03-01 16:22:51 +02:00
Greg Troxel
e273900e87 AutoInterface: Drop embedded scope identifier on fe80::
The code previously dropped scope identifiers expressed as a trailing
"%ifname", which happens on macOS.  On NetBSD and OpenBSD (and likely
FreeBSD, not tested), the scope identifier is embedded.  Drop that
form of identifier as well, because we keep address and ifname
separate, and because the scope identifier must not be part of
computing the hash of the address.

Resolves #240, failure to peer on NetBSD and OpenBSD.
2023-02-28 10:19:46 -05:00
Aaron Heise
d2d121d49f
Fix broken Channel test 2023-02-28 08:38:36 -06:00
Aaron Heise
9963cf37b8
Fix exceptions on Channel shutdown 2023-02-28 08:38:23 -06:00
Aaron Heise
72300cc821
Revert "Only send proof if link is still active" 2023-02-28 08:24:13 -06:00
Aaron Heise
8168d9bb92
Only send proof if link is still active 2023-02-28 08:13:07 -06:00
Aaron Heise
8f0151fed6
Tidy up PR 2023-02-27 21:33:50 -06:00
Aaron Heise
d3c4928eda
Tidy up PR 2023-02-27 21:31:41 -06:00
Aaron Heise
68f95cd80b
Tidy up PR 2023-02-27 21:30:13 -06:00
Aaron Heise
42935c8238
Make the PR have zero deletions 2023-02-27 21:15:25 -06:00
Aaron Heise
118acf77b8
Fix up documentation even more 2023-02-27 21:10:28 -06:00
Aaron Heise
661964277f
Fix up documentation for building 2023-02-27 19:05:25 -06:00
Aaron Heise
464dc23ff0
Add some internal documenation 2023-02-27 17:36:04 -06:00
Aaron Heise
44dc2d06c6
Add channel tests to all test suite
Also print name in each test
2023-02-26 11:47:46 -06:00
Aaron Heise
c00b592ed9
System-reserved channel message types
- a message handler can return logical True to prevent subsequent message handlers from running
- Message types >= 0xff00 are reserved for system/framework messages
2023-02-26 11:39:49 -06:00
Aaron Heise
e005826151
Allow channel message handlers to short circuit
- a message handler can return logical True to prevent subsequent message handlers from running
2023-02-26 11:23:38 -06:00
Aaron Heise
a61b15cf6a
Added channel example 2023-02-26 07:26:12 -06:00
Aaron Heise
fe3a3e22f7
Expose Channel on Link
Separates channel interface from link

Also added: allow multiple message handlers
2023-02-26 07:25:49 -06:00
Aaron Heise
68cb4a6740
Initial work on Channel 2023-02-25 18:23:25 -06:00
Mark Qvist
9f06bed34c Updated readme and roadmap 2023-02-23 17:27:05 +01:00
Mark Qvist
3b1936ef48 Added EPUB output to documentation build 2023-02-23 17:25:38 +01:00
Michael Faragher
5b3d26a90a Additional error messages for offline flashing. 2023-02-22 12:49:24 -06:00
44 changed files with 2157 additions and 31 deletions

395
Examples/Channel.py Normal file
View File

@ -0,0 +1,395 @@
##########################################################
# This RNS example demonstrates how to set up a link to #
# a destination, and pass structured messages over it #
# using a channel. #
##########################################################
import os
import sys
import time
import argparse
from datetime import datetime
import RNS
from RNS.vendor import umsgpack
# Let's define an app name. We'll use this for all
# destinations we create. Since this echo example
# is part of a range of example utilities, we'll put
# them all within the app namespace "example_utilities"
APP_NAME = "example_utilities"
##########################################################
#### Shared Objects ######################################
##########################################################
# Channel data must be structured in a subclass of
# MessageBase. This ensures that the channel will be able
# to serialize and deserialize the object and multiplex it
# with other objects. Both ends of a link will need the
# same object definitions to be able to communicate over
# a channel.
#
# Note: The objects we wish to use over the channel must
# be registered with the channel, and each link has a
# different channel instance. See the client_connected
# and link_established functions in this example to see
# how message types are registered.
# Let's make a simple message class called StringMessage
# that will convey a string with a timestamp.
class StringMessage(RNS.MessageBase):
# The MSGTYPE class variable needs to be assigned a
# 2 byte integer value. This identifier allows the
# channel to look up your message's constructor when a
# message arrives over the channel.
#
# MSGTYPE must be unique across all message types we
# register with the channel. MSGTYPEs >= 0xf000 are
# reserved for the system.
MSGTYPE = 0x0101
# The constructor of our object must be callable with
# no arguments. We can have parameters, but they must
# have a default assignment.
#
# This is needed so the channel can create an empty
# version of our message into which the incoming
# message can be unpacked.
def __init__(self, data=None):
self.data = data
self.timestamp = datetime.now()
# Finally, our message needs to implement functions
# the channel can call to pack and unpack our message
# to/from the raw packet payload. We'll use the
# umsgpack package bundled with RNS. We could also use
# the struct package bundled with Python if we wanted
# more control over the structure of the packed bytes.
#
# Also note that packed message objects must fit
# entirely in one packet. The number of bytes
# available for message payloads can be queried from
# the channel using the Channel.MDU property. The
# channel MDU is slightly less than the link MDU due
# to encoding the message header.
# The pack function encodes the message contents into
# a byte stream.
def pack(self) -> bytes:
return umsgpack.packb((self.data, self.timestamp))
# And the unpack function decodes a byte stream into
# the message contents.
def unpack(self, raw):
self.data, self.timestamp = umsgpack.unpackb(raw)
##########################################################
#### Server Part #########################################
##########################################################
# A reference to the latest client link that connected
latest_client_link = None
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
# We create a destination that clients can connect to. We
# want clients to create links to this destination, so we
# need to create a "single" destination type.
server_destination = RNS.Destination(
server_identity,
RNS.Destination.IN,
RNS.Destination.SINGLE,
APP_NAME,
"channelexample"
)
# We configure a function that will get called every time
# a new client creates a link to this destination.
server_destination.set_link_established_callback(client_connected)
# Everything's ready!
# Let's Wait for client requests or user input
server_loop(server_destination)
def server_loop(destination):
# Let the user know that everything is ready
RNS.log(
"Link example "+
RNS.prettyhexrep(destination.hash)+
" running, waiting for a connection."
)
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server
# destination on the network, which will let clients
# know how to create messages directed towards it.
while True:
entered = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
# When a client establishes a link to our server
# destination, this function will be called with
# a reference to the link.
def client_connected(link):
global latest_client_link
latest_client_link = link
RNS.log("Client connected")
link.set_link_closed_callback(client_disconnected)
# Register message types and add callback to channel
channel = link.get_channel()
channel.register_message_type(StringMessage)
channel.add_message_handler(server_message_received)
def client_disconnected(link):
RNS.log("Client disconnected")
def server_message_received(message):
"""
A message handler
@param message: An instance of a subclass of MessageBase
@return: True if message was handled
"""
global latest_client_link
# When a message is received over any active link,
# the replies will all be directed to the last client
# that connected.
# In a message handler, any deserializable message
# that arrives over the link's channel will be passed
# to all message handlers, unless a preceding handler indicates it
# has handled the message.
#
#
if isinstance(message, StringMessage):
RNS.log("Received data on the link: " + message.data + " (message created at " + str(message.timestamp) + ")")
reply_message = StringMessage("I received \""+message.data+"\" over the link")
latest_client_link.get_channel().send(reply_message)
# Incoming messages are sent to each message
# handler added to the channel, in the order they
# were added.
# If any message handler returns True, the message
# is considered handled and any subsequent
# handlers are skipped.
return True
##########################################################
#### Client Part #########################################
##########################################################
# A reference to the server link
server_link = None
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Check if we know a path to the destination
if not RNS.Transport.has_path(destination_hash):
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
RNS.Transport.request_path(destination_hash)
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
# Recall the server identity
server_identity = RNS.Identity.recall(destination_hash)
# Inform the user that we'll begin connecting
RNS.log("Establishing link with server...")
# When the server identity is known, we set
# up a destination
server_destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"channelexample"
)
# And create a link
link = RNS.Link(server_destination)
# We set a callback that will get executed
# every time a packet is received over the
# link
link.set_packet_callback(client_message_received)
# We'll also set up functions to inform the
# user when the link is established or closed
link.set_link_established_callback(link_established)
link.set_link_closed_callback(link_closed)
# Everything is set up, so let's enter a loop
# for the user to interact with the example
client_loop()
def client_loop():
global server_link
# Wait for the link to become active
while not server_link:
time.sleep(0.1)
should_quit = False
while not should_quit:
try:
print("> ", end=" ")
text = input()
# Check if we should quit the example
if text == "quit" or text == "q" or text == "exit":
should_quit = True
server_link.teardown()
# If not, send the entered text over the link
if text != "":
message = StringMessage(text)
packed_size = len(message.pack())
channel = server_link.get_channel()
if channel.is_ready_to_send():
if packed_size <= channel.MDU:
channel.send(message)
else:
RNS.log(
"Cannot send this packet, the data size of "+
str(packed_size)+" bytes exceeds the link packet MDU of "+
str(channel.MDU)+" bytes",
RNS.LOG_ERROR
)
else:
RNS.log("Channel is not ready to send, please wait for " +
"pending messages to complete.", RNS.LOG_ERROR)
except Exception as e:
RNS.log("Error while sending data over the link: "+str(e))
should_quit = True
server_link.teardown()
# This function is called when a link
# has been established with the server
def link_established(link):
# We store a reference to the link
# instance for later use
global server_link
server_link = link
# Register messages and add handler to channel
channel = link.get_channel()
channel.register_message_type(StringMessage)
channel.add_message_handler(client_message_received)
# Inform the user that the server is
# connected
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
# When a link is closed, we'll inform the
# user, and exit the program
def link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
RNS.log("The link timed out, exiting now")
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
RNS.log("The link was closed by the server, exiting now")
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
# When a packet is received over the link, we
# simply print out the data.
def client_message_received(message):
if isinstance(message, StringMessage):
RNS.log("Received data on the link: " + message.data + " (message created at " + str(message.timestamp) + ")")
print("> ", end=" ")
sys.stdout.flush()
##########################################################
#### Program Startup #####################################
##########################################################
# This part of the program runs at startup,
# and parses input of from the user, and then
# starts up the desired program mode.
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="Simple link example")
parser.add_argument(
"-s",
"--server",
action="store_true",
help="wait for incoming link requests from clients"
)
parser.add_argument(
"--config",
action="store",
default=None,
help="path to alternative Reticulum config directory",
type=str
)
parser.add_argument(
"destination",
nargs="?",
default=None,
help="hexadecimal hash of the server destination",
type=str
)
args = parser.parse_args()
if args.config:
configarg = args.config
else:
configarg = None
if args.server:
server(configarg)
else:
if (args.destination == None):
print("")
parser.print_help()
print("")
else:
client(args.destination, configarg)
except KeyboardInterrupt:
print("")
exit()

View File

@ -210,6 +210,7 @@ def client(destination_hexhash, configpath, timeout=None):
# If we do not know this destination, tell the
# user to wait for an announce to arrive.
RNS.log("Destination is not yet known. Requesting path...")
RNS.log("Hit enter to manually retry once an announce is received.")
RNS.Transport.request_path(destination_hash)
# This function is called when our reply destination

View File

@ -47,7 +47,7 @@ documentation:
make -C docs html
manual:
make -C docs latexpdf
make -C docs latexpdf epub
release: test remove_symlinks build_wheel build_pure_wheel documentation manual create_symlinks

View File

@ -71,6 +71,7 @@ To learn more about the direction and future of Reticulum, please see the [Devel
If you want to quickly get an idea of what Reticulum can do, take a look at the
following resources.
- You can use the [rnsh](https://github.com/acehoss/rnsh) program to establish remote shell sessions over Reticulum.
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
- The Android, Linux and macOS app [Sideband](https://github.com/markqvist/Sideband) has a graphical interface and focuses on ease of use.
- [LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum

521
RNS/Channel.py Normal file
View File

@ -0,0 +1,521 @@
# MIT License
#
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import collections
import enum
import threading
import time
from types import TracebackType
from typing import Type, Callable, TypeVar, Generic, NewType
import abc
import contextlib
import struct
import RNS
from abc import ABC, abstractmethod
TPacket = TypeVar("TPacket")
class ChannelOutletBase(ABC, Generic[TPacket]):
"""
An abstract transport layer interface used by Channel.
DEPRECATED: This was created for testing; eventually
Channel will use Link or a LinkBase interface
directly.
"""
@abstractmethod
def send(self, raw: bytes) -> TPacket:
raise NotImplemented()
@abstractmethod
def resend(self, packet: TPacket) -> TPacket:
raise NotImplemented()
@property
@abstractmethod
def mdu(self):
raise NotImplemented()
@property
@abstractmethod
def rtt(self):
raise NotImplemented()
@property
@abstractmethod
def is_usable(self):
raise NotImplemented()
@abstractmethod
def get_packet_state(self, packet: TPacket) -> MessageState:
raise NotImplemented()
@abstractmethod
def timed_out(self):
raise NotImplemented()
@abstractmethod
def __str__(self):
raise NotImplemented()
@abstractmethod
def set_packet_timeout_callback(self, packet: TPacket, callback: Callable[[TPacket], None] | None,
timeout: float | None = None):
raise NotImplemented()
@abstractmethod
def set_packet_delivered_callback(self, packet: TPacket, callback: Callable[[TPacket], None] | None):
raise NotImplemented()
@abstractmethod
def get_packet_id(self, packet: TPacket) -> any:
raise NotImplemented()
class CEType(enum.IntEnum):
"""
ChannelException type codes
"""
ME_NO_MSG_TYPE = 0
ME_INVALID_MSG_TYPE = 1
ME_NOT_REGISTERED = 2
ME_LINK_NOT_READY = 3
ME_ALREADY_SENT = 4
ME_TOO_BIG = 5
class ChannelException(Exception):
"""
An exception thrown by Channel, with a type code.
"""
def __init__(self, ce_type: CEType, *args):
super().__init__(args)
self.type = ce_type
class MessageState(enum.IntEnum):
"""
Set of possible states for a Message
"""
MSGSTATE_NEW = 0
MSGSTATE_SENT = 1
MSGSTATE_DELIVERED = 2
MSGSTATE_FAILED = 3
class MessageBase(abc.ABC):
"""
Base type for any messages sent or received on a Channel.
Subclasses must define the two abstract methods as well as
the ``MSGTYPE`` class variable.
"""
# MSGTYPE must be unique within all classes sent over a
# channel. Additionally, MSGTYPE > 0xf000 are reserved.
MSGTYPE = None
"""
Defines a unique identifier for a message class.
* Must be unique within all classes registered with a ``Channel``
* Must be less than ``0xf000``. Values greater than or equal to ``0xf000`` are reserved.
"""
@abstractmethod
def pack(self) -> bytes:
"""
Create and return the binary representation of the message
:return: binary representation of message
"""
raise NotImplemented()
@abstractmethod
def unpack(self, raw: bytes):
"""
Populate message from binary representation
:param raw: binary representation
"""
raise NotImplemented()
MessageCallbackType = NewType("MessageCallbackType", Callable[[MessageBase], bool])
class Envelope:
"""
Internal wrapper used to transport messages over a channel and
track its state within the channel framework.
"""
def unpack(self, message_factories: dict[int, Type]) -> MessageBase:
msgtype, self.sequence, length = struct.unpack(">HHH", self.raw[:6])
raw = self.raw[6:]
ctor = message_factories.get(msgtype, None)
if ctor is None:
raise ChannelException(CEType.ME_NOT_REGISTERED, f"Unable to find constructor for Channel MSGTYPE {hex(msgtype)}")
message = ctor()
message.unpack(raw)
return message
def pack(self) -> bytes:
if self.message.__class__.MSGTYPE is None:
raise ChannelException(CEType.ME_NO_MSG_TYPE, f"{self.message.__class__} lacks MSGTYPE")
data = self.message.pack()
self.raw = struct.pack(">HHH", self.message.MSGTYPE, self.sequence, len(data)) + data
return self.raw
def __init__(self, outlet: ChannelOutletBase, message: MessageBase = None, raw: bytes = None, sequence: int = None):
self.ts = time.time()
self.id = id(self)
self.message = message
self.raw = raw
self.packet: TPacket = None
self.sequence = sequence
self.outlet = outlet
self.tries = 0
self.tracked = False
class Channel(contextlib.AbstractContextManager):
"""
Provides reliable delivery of messages over
a link.
``Channel`` differs from ``Request`` and
``Resource`` in some important ways:
**Continuous**
Messages can be sent or received as long as
the ``Link`` is open.
**Bi-directional**
Messages can be sent in either direction on
the ``Link``; neither end is the client or
server.
**Size-constrained**
Messages must be encoded into a single packet.
``Channel`` is similar to ``Packet``, except that it
provides reliable delivery (automatic retries) as well
as a structure for exchanging several types of
messages over the ``Link``.
``Channel`` is not instantiated directly, but rather
obtained from a ``Link`` with ``get_channel()``.
"""
def __init__(self, outlet: ChannelOutletBase):
"""
@param outlet:
"""
self._outlet = outlet
self._lock = threading.RLock()
self._tx_ring: collections.deque[Envelope] = collections.deque()
self._rx_ring: collections.deque[Envelope] = collections.deque()
self._message_callbacks: [MessageCallbackType] = []
self._next_sequence = 0
self._message_factories: dict[int, Type[MessageBase]] = {}
self._max_tries = 5
def __enter__(self) -> Channel:
return self
def __exit__(self, __exc_type: Type[BaseException] | None, __exc_value: BaseException | None,
__traceback: TracebackType | None) -> bool | None:
self._shutdown()
return False
def register_message_type(self, message_class: Type[MessageBase]):
"""
Register a message class for reception over a ``Channel``.
Message classes must extend ``MessageBase``.
:param message_class: Class to register
"""
self._register_message_type(message_class, is_system_type=False)
def _register_message_type(self, message_class: Type[MessageBase], *, is_system_type: bool = False):
with self._lock:
if not issubclass(message_class, MessageBase):
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
f"{message_class} is not a subclass of {MessageBase}.")
if message_class.MSGTYPE is None:
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
f"{message_class} has invalid MSGTYPE class attribute.")
if message_class.MSGTYPE >= 0xf000 and not is_system_type:
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
f"{message_class} has system-reserved message type.")
try:
message_class()
except Exception as ex:
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
f"{message_class} raised an exception when constructed with no arguments: {ex}")
self._message_factories[message_class.MSGTYPE] = message_class
def add_message_handler(self, callback: MessageCallbackType):
"""
Add a handler for incoming messages. A handler
has the following signature:
``(message: MessageBase) -> bool``
Handlers are processed in the order they are
added. If any handler returns True, processing
of the message stops; handlers after the
returning handler will not be called.
:param callback: Function to call
"""
with self._lock:
if callback not in self._message_callbacks:
self._message_callbacks.append(callback)
def remove_message_handler(self, callback: MessageCallbackType):
"""
Remove a handler added with ``add_message_handler``.
:param callback: handler to remove
"""
with self._lock:
self._message_callbacks.remove(callback)
def _shutdown(self):
with self._lock:
self._message_callbacks.clear()
self._clear_rings()
def _clear_rings(self):
with self._lock:
for envelope in self._tx_ring:
if envelope.packet is not None:
self._outlet.set_packet_timeout_callback(envelope.packet, None)
self._outlet.set_packet_delivered_callback(envelope.packet, None)
self._tx_ring.clear()
self._rx_ring.clear()
def _emplace_envelope(self, envelope: Envelope, ring: collections.deque[Envelope]) -> bool:
with self._lock:
i = 0
for existing in ring:
if existing.sequence > envelope.sequence \
and not existing.sequence // 2 > envelope.sequence: # account for overflow
ring.insert(i, envelope)
return True
if existing.sequence == envelope.sequence:
RNS.log(f"Envelope: Emplacement of duplicate 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()
for cb in cbs:
try:
if cb(message):
return
except Exception as ex:
RNS.log(f"Channel: Error running message callback: {ex}", 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)
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)
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}")
def is_ready_to_send(self) -> bool:
"""
Check if ``Channel`` is ready to send.
: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:
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):
RNS.log("Channel: Link has a pending message.", RNS.LOG_EXTREME)
return False
return True
def _packet_tx_op(self, packet: TPacket, op: Callable[[TPacket], bool]):
with self._lock:
envelope = next(filter(lambda e: self._outlet.get_packet_id(e.packet) == self._outlet.get_packet_id(packet),
self._tx_ring), None)
if envelope and op(envelope):
envelope.tracked = False
if envelope in self._tx_ring:
self._tx_ring.remove(envelope)
else:
RNS.log("Channel: Envelope not found in TX ring", RNS.LOG_DEBUG)
if not envelope:
RNS.log("Channel: Spurious message received.", RNS.LOG_EXTREME)
def _packet_delivered(self, packet: TPacket):
self._packet_tx_op(packet, lambda env: True)
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)
self._shutdown() # start on separate thread?
self._outlet.timed_out()
return True
envelope.tries += 1
self._outlet.resend(envelope.packet)
return False
self._packet_tx_op(packet, retry_envelope)
def send(self, message: MessageBase) -> Envelope:
"""
Send a message. If a message send is attempted and
``Channel`` is not ready, an exception is thrown.
:param message: an instance of a ``MessageBase`` subclass
"""
envelope: Envelope | None = None
with self._lock:
if not self.is_ready_to_send():
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
envelope = Envelope(self._outlet, message=message, sequence=self._next_sequence)
self._next_sequence = (self._next_sequence + 1) % 0x10000
self._emplace_envelope(envelope, self._tx_ring)
if envelope is None:
raise BlockingIOError()
envelope.pack()
if len(envelope.raw) > self._outlet.mdu:
raise ChannelException(CEType.ME_TOO_BIG, f"Packed message too big for packet: {len(envelope.raw)} > {self._outlet.mdu}")
envelope.packet = self._outlet.send(envelope.raw)
envelope.tries += 1
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout)
return envelope
@property
def MDU(self):
"""
Maximum Data Unit: the number of bytes available
for a message to consume in a single send. This
value is adjusted from the ``Link`` MDU to accommodate
message header information.
:return: number of bytes available
"""
return self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
class LinkChannelOutlet(ChannelOutletBase):
"""
An implementation of ChannelOutletBase for RNS.Link.
Allows Channel to send packets over an RNS Link with
Packets.
:param link: RNS Link to wrap
"""
def __init__(self, link: RNS.Link):
self.link = link
def send(self, raw: bytes) -> RNS.Packet:
packet = RNS.Packet(self.link, raw, context=RNS.Packet.CHANNEL)
packet.send()
return packet
def resend(self, packet: RNS.Packet) -> RNS.Packet:
if not packet.resend():
RNS.log("Failed to resend packet", RNS.LOG_ERROR)
return packet
@property
def mdu(self):
return self.link.MDU
@property
def rtt(self):
return self.link.rtt
@property
def is_usable(self):
return True # had issues looking at Link.status
def get_packet_state(self, packet: TPacket) -> MessageState:
status = packet.receipt.get_status()
if status == RNS.PacketReceipt.SENT:
return MessageState.MSGSTATE_SENT
if status == RNS.PacketReceipt.DELIVERED:
return MessageState.MSGSTATE_DELIVERED
if status == RNS.PacketReceipt.FAILED:
return MessageState.MSGSTATE_FAILED
else:
raise Exception(f"Unexpected receipt state: {status}")
def timed_out(self):
self.link.teardown()
def __str__(self):
return f"{self.__class__.__name__}({self.link})"
def set_packet_timeout_callback(self, packet: RNS.Packet, callback: Callable[[RNS.Packet], None] | None,
timeout: float | None = None):
if timeout:
packet.receipt.set_timeout(timeout)
def inner(receipt: RNS.PacketReceipt):
callback(packet)
if packet and packet.receipt:
packet.receipt.set_timeout_callback(inner if callback else None)
def set_packet_delivered_callback(self, packet: RNS.Packet, callback: Callable[[RNS.Packet], None] | None):
def inner(receipt: RNS.PacketReceipt):
callback(packet)
if packet and packet.receipt:
packet.receipt.set_delivery_callback(inner if callback else None)
def get_packet_id(self, packet: RNS.Packet) -> any:
return packet.get_hash()

View File

@ -39,9 +39,6 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
if salt == None or len(salt) == 0:
salt = bytes([0] * hash_len)
if salt == None:
salt = b""
if context == None:
context = b""

View File

@ -23,6 +23,7 @@
from .Interface import Interface
import socketserver
import threading
import re
import socket
import struct
import time
@ -53,6 +54,13 @@ class AutoInterface(Interface):
return AutoInterfaceHandler(callback, *args, **keys)
return create_handler
def descope_linklocal(self, link_local_addr):
# Drop scope specifier expressd as %ifname (macOS)
link_local_addr = link_local_addr.split("%")[0]
# Drop embedded scope specifier (NetBSD, OpenBSD)
link_local_addr = re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr)
return link_local_addr
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:
@ -158,9 +166,9 @@ class AutoInterface(Interface):
for address in addresses[self.netifaces.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
link_local_addr = address["addr"]
self.link_local_addresses.append(link_local_addr.split("%")[0])
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
link_local_addr = self.descope_linklocal(address["addr"])
self.link_local_addresses.append(link_local_addr)
self.adopted_interfaces[ifname] = link_local_addr
self.multicast_echoes[ifname] = time.time()
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
@ -278,7 +286,7 @@ class AutoInterface(Interface):
for address in addresses[self.netifaces.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
link_local_addr = address["addr"].split("%")[0]
link_local_addr = self.descope_linklocal(address["addr"])
if link_local_addr != self.adopted_interfaces[ifname]:
old_link_local_address = self.adopted_interfaces[ifname]
RNS.log("Replacing link-local address "+str(old_link_local_address)+" for "+str(ifname)+" with "+str(link_local_addr), RNS.LOG_DEBUG)

View File

@ -22,6 +22,7 @@
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
from RNS.Cryptography import Fernet
from RNS.Channel import Channel, LinkChannelOutlet
from time import sleep
from .vendor import umsgpack as umsgpack
@ -163,6 +164,7 @@ class Link:
self.destination = destination
self.attached_interface = None
self.__remote_identity = None
self._channel = None
if self.destination == None:
self.initiator = False
self.prv = X25519PrivateKey.generate()
@ -462,6 +464,8 @@ class Link:
resource.cancel()
for resource in self.outgoing_resources:
resource.cancel()
if self._channel:
self._channel._shutdown()
self.prv = None
self.pub = None
@ -642,6 +646,16 @@ class Link:
if pending_request.request_id == resource.request_id:
pending_request.request_timed_out(None)
def get_channel(self):
"""
Get the ``Channel`` for this link.
:return: ``Channel`` object
"""
if self._channel is None:
self._channel = Channel(LinkChannelOutlet(self))
return self._channel
def receive(self, packet):
self.watchdog_lock = True
if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])):
@ -788,6 +802,14 @@ class Link:
for resource in self.incoming_resources:
resource.receive_part(packet)
elif packet.context == RNS.Packet.CHANNEL:
if not self._channel:
RNS.log(f"Channel data received without open channel", RNS.LOG_DEBUG)
else:
plaintext = self.decrypt(packet.data)
self._channel._receive(plaintext)
packet.prove()
elif packet.packet_type == RNS.Packet.PROOF:
if packet.context == RNS.Packet.RESOURCE_PRF:
resource_hash = packet.data[0:RNS.Identity.HASHLENGTH//8]

View File

@ -75,6 +75,7 @@ class Packet:
PATH_RESPONSE = 0x0B # Packet is a response to a path request
COMMAND = 0x0C # Packet is a command
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
CHANNEL = 0x0E # Packet contains link channel data
KEEPALIVE = 0xFA # Packet is a keepalive packet
LINKIDENTIFY = 0xFB # Packet is a link peer identification proof
LINKCLOSE = 0xFC # Packet is a link close message

View File

@ -831,6 +831,9 @@ class RNode():
print(" WARNING! This device is NOT verifiable and should NOT be trusted.")
print(" Someone could have added privacy-breaking or malicious code to it.")
print(" ")
print(" Please verify the signing key is present on this machine.")
print(" Autogenerated keys will not match another machine's signature.")
print(" ")
print(" Proceed at your own risk and responsibility! If you created this")
print(" device yourself, please read the documentation on how to sign your")
print(" device to avoid this warning.")
@ -948,6 +951,7 @@ def ensure_firmware_file(fw_filename):
RNS.log("Failed to retrive latest version information for your board.")
RNS.log("Check your internet connection and try again.")
RNS.log("If you don't have Internet access currently, use the --fw-version option to manually specify a version.")
RNS.log("You can also use --extract to copy the firmware from a known-good RNode of the same model.")
exit()
import shutil
@ -972,6 +976,7 @@ def ensure_firmware_file(fw_filename):
os.makedirs(UPD_DIR+"/"+selected_version)
if not os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename):
RNS.log("Firmware "+UPD_DIR+"/"+selected_version+"/"+fw_filename+" not found.")
RNS.log("Downloading missing firmware file: "+fw_filename+" for version "+selected_version)
urlretrieve(update_target_url, UPD_DIR+"/"+selected_version+"/"+fw_filename)
RNS.log("Firmware file downloaded")
@ -986,6 +991,7 @@ def ensure_firmware_file(fw_filename):
selected_hash = release_info.split()[1]
except Exception as e:
RNS.log("Could not read locally cached release information.")
RNS.log("Ensure "+UPD_DIR+"/"+selected_version+"/"+fw_filename+".version exists and has the correct format and hash.")
RNS.log("You can clear the cache with the --clear-cache option and try again.")
if selected_hash == None:
@ -1132,6 +1138,11 @@ def main():
if args.fw_version != None:
selected_version = args.fw_version
try:
check_float = float(selected_version)
except ValueError:
RNS.log("Selected version \""+selected_version+"\" does not appear to be a number.")
exit()
if args.force_update:
force_update = True

View File

@ -32,6 +32,7 @@ from ._version import __version__
from .Reticulum import Reticulum
from .Identity import Identity
from .Link import Link, RequestReceipt
from .Channel import MessageBase
from .Transport import Transport
from .Destination import Destination
from .Packet import Packet

View File

@ -21,6 +21,7 @@ For each release cycle of Reticulum, improvements and additions from the five [P
- [x] Add bluetooth pairing code output to rnodeconf
- [x] Add `rnid` utility with encryption, signing and Identity funcionality
- [x] JSON output mode for rnstatus
- [ ] Add `Buffer` class to the API
- [ ] Add automatic retries to all use cases of the `Request` API
- [ ] Updating the documentation to reflect recent changes and improvements
- Targets for related applications

View File

@ -30,3 +30,8 @@ help:
cp -r build/latex/reticulumnetworkstack.pdf ./Reticulum\ Manual.pdf; \
echo "PDF Manual Generated"; \
fi
@if [ $@ = "epub" ]; then \
cp -r build/epub/ReticulumNetworkStack.epub ./Reticulum\ Manual.epub; \
echo "EPUB Manual Generated"; \
fi

BIN
docs/Reticulum Manual.epub Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 9738e36d7743271d1e6118c152b32349
config: f6ba2c305fffe594c412b86b0152889d
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@ -92,6 +92,18 @@ The *Request* example explores sendig requests and receiving responses.
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
.. _example-channel:
Channel
=======
The *Channel* example explores using a ``Channel`` to send structured
data between peers of a ``Link``.
.. literalinclude:: ../../Examples/Channel.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py>`_.
.. _example-filetransfer:
Filetransfer

View File

@ -42,6 +42,13 @@ transceivers or infrastructure just to try it out. Launching the programs on sep
devices connected to the same WiFi network is enough to get started, and physical
radio interfaces can then be added later.
Remote Shell
^^^^^^^^^^^^
The `rnsh <https://github.com/acehoss/rnsh>`_ program lets you establish fully interactive
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
remote system, and is similar to how ``ssh`` works.
Nomad Network
^^^^^^^^^^^^^

View File

@ -5,6 +5,10 @@ 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
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
Table Of Contents

View File

@ -121,6 +121,34 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
:members:
.. _api-channel:
.. only:: html
|start-h3| Channel |end-h3|
.. only:: latex
Channel
-------
.. autoclass:: RNS.Channel.Channel()
:members:
.. _api-messsagebase:
.. only:: html
|start-h3| MessageBase |end-h3|
.. only:: latex
MessageBase
-----------
.. autoclass:: RNS.MessageBase()
:members:
.. _api-transport:
.. only:: html

View File

@ -861,6 +861,7 @@ the Packet interface.</p>
<span class="c1"># If we do not know this destination, tell the</span>
<span class="c1"># user to wait for an announce to arrive.</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Destination is not yet known. Requesting path...&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually retry once an announce is received.&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="c1"># This function is called when our reply destination</span>
@ -1898,6 +1899,409 @@ the link has been established.</p>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py</a>.</p>
</section>
<section id="channel">
<span id="example-channel"></span><h2>Channel<a class="headerlink" href="#channel" title="Permalink to this heading">#</a></h2>
<p>The <em>Channel</em> example explores using a <code class="docutils literal notranslate"><span class="pre">Channel</span></code> to send structured
data between peers of a <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
<span class="c1"># This RNS example demonstrates how to set up a link to #</span>
<span class="c1"># a destination, and pass structured messages over it #</span>
<span class="c1"># using a channel. #</span>
<span class="c1">##########################################################</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="kn">import</span> <span class="nn">RNS</span>
<span class="kn">from</span> <span class="nn">RNS.vendor</span> <span class="kn">import</span> <span class="n">umsgpack</span>
<span class="c1"># Let&#39;s define an app name. We&#39;ll use this for all</span>
<span class="c1"># destinations we create. Since this echo example</span>
<span class="c1"># is part of a range of example utilities, we&#39;ll put</span>
<span class="c1"># them all within the app namespace &quot;example_utilities&quot;</span>
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">&quot;example_utilities&quot;</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Shared Objects ######################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># Channel data must be structured in a subclass of</span>
<span class="c1"># MessageBase. This ensures that the channel will be able</span>
<span class="c1"># to serialize and deserialize the object and multiplex it</span>
<span class="c1"># with other objects. Both ends of a link will need the</span>
<span class="c1"># same object definitions to be able to communicate over</span>
<span class="c1"># a channel.</span>
<span class="c1">#</span>
<span class="c1"># Note: The objects we wish to use over the channel must</span>
<span class="c1"># be registered with the channel, and each link has a</span>
<span class="c1"># different channel instance. See the client_connected</span>
<span class="c1"># and link_established functions in this example to see</span>
<span class="c1"># how message types are registered.</span>
<span class="c1"># Let&#39;s make a simple message class called StringMessage</span>
<span class="c1"># that will convey a string with a timestamp.</span>
<span class="k">class</span> <span class="nc">StringMessage</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">MessageBase</span><span class="p">):</span>
<span class="c1"># The MSGTYPE class variable needs to be assigned a</span>
<span class="c1"># 2 byte integer value. This identifier allows the</span>
<span class="c1"># channel to look up your message&#39;s constructor when a</span>
<span class="c1"># message arrives over the channel.</span>
<span class="c1">#</span>
<span class="c1"># MSGTYPE must be unique across all message types we</span>
<span class="c1"># register with the channel. MSGTYPEs &gt;= 0xf000 are</span>
<span class="c1"># reserved for the system.</span>
<span class="n">MSGTYPE</span> <span class="o">=</span> <span class="mh">0x0101</span>
<span class="c1"># The constructor of our object must be callable with</span>
<span class="c1"># no arguments. We can have parameters, but they must</span>
<span class="c1"># have a default assignment.</span>
<span class="c1">#</span>
<span class="c1"># This is needed so the channel can create an empty</span>
<span class="c1"># version of our message into which the incoming</span>
<span class="c1"># message can be unpacked.</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">data</span>
<span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
<span class="c1"># Finally, our message needs to implement functions</span>
<span class="c1"># the channel can call to pack and unpack our message</span>
<span class="c1"># to/from the raw packet payload. We&#39;ll use the</span>
<span class="c1"># umsgpack package bundled with RNS. We could also use</span>
<span class="c1"># the struct package bundled with Python if we wanted</span>
<span class="c1"># more control over the structure of the packed bytes.</span>
<span class="c1">#</span>
<span class="c1"># Also note that packed message objects must fit</span>
<span class="c1"># entirely in one packet. The number of bytes</span>
<span class="c1"># available for message payloads can be queried from</span>
<span class="c1"># the channel using the Channel.MDU property. The</span>
<span class="c1"># channel MDU is slightly less than the link MDU due</span>
<span class="c1"># to encoding the message header.</span>
<span class="c1"># The pack function encodes the message contents into</span>
<span class="c1"># a byte stream.</span>
<span class="k">def</span> <span class="nf">pack</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
<span class="k">return</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">packb</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="p">))</span>
<span class="c1"># And the unpack function decodes a byte stream into</span>
<span class="c1"># the message contents.</span>
<span class="k">def</span> <span class="nf">unpack</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">raw</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">unpackb</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Server Part #########################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># A reference to the latest client link that connected</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a server</span>
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our link example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="c1"># We create a destination that clients can connect to. We</span>
<span class="c1"># want clients to create links to this destination, so we</span>
<span class="c1"># need to create a &quot;single&quot; destination type.</span>
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
<span class="n">server_identity</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
<span class="n">APP_NAME</span><span class="p">,</span>
<span class="s2">&quot;channelexample&quot;</span>
<span class="p">)</span>
<span class="c1"># We configure a function that will get called every time</span>
<span class="c1"># a new client creates a link to this destination.</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s Wait for client requests or user input</span>
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
<span class="c1"># Let the user know that everything is ready</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Link example &quot;</span><span class="o">+</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
<span class="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C to quit)&quot;</span><span class="p">)</span>
<span class="c1"># We enter a loop that runs until the users exits.</span>
<span class="c1"># If the user hits enter, we will announce our server</span>
<span class="c1"># destination on the network, which will let clients</span>
<span class="c1"># know how to create messages directed towards it.</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Sent announce from &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
<span class="c1"># When a client establishes a link to our server</span>
<span class="c1"># destination, this function will be called with</span>
<span class="c1"># a reference to the link.</span>
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="k">global</span> <span class="n">latest_client_link</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client connected&quot;</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="c1"># Register message types and add callback to channel</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">register_message_type</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">add_message_handler</span><span class="p">(</span><span class="n">server_message_received</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client disconnected&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">server_message_received</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> A message handler</span>
<span class="sd"> @param message: An instance of a subclass of MessageBase</span>
<span class="sd"> @return: True if message was handled</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">global</span> <span class="n">latest_client_link</span>
<span class="c1"># When a message is received over any active link,</span>
<span class="c1"># the replies will all be directed to the last client</span>
<span class="c1"># that connected.</span>
<span class="c1"># In a message handler, any deserializable message</span>
<span class="c1"># that arrives over the link&#39;s channel will be passed</span>
<span class="c1"># to all message handlers, unless a preceding handler indicates it</span>
<span class="c1"># has handled the message.</span>
<span class="c1">#</span>
<span class="c1">#</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringMessage</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received data on the link: &quot;</span> <span class="o">+</span> <span class="n">message</span><span class="o">.</span><span class="n">data</span> <span class="o">+</span> <span class="s2">&quot; (message created at &quot;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot;)&quot;</span><span class="p">)</span>
<span class="n">reply_message</span> <span class="o">=</span> <span class="n">StringMessage</span><span class="p">(</span><span class="s2">&quot;I received </span><span class="se">\&quot;</span><span class="s2">&quot;</span><span class="o">+</span><span class="n">message</span><span class="o">.</span><span class="n">data</span><span class="o">+</span><span class="s2">&quot;</span><span class="se">\&quot;</span><span class="s2"> over the link&quot;</span><span class="p">)</span>
<span class="n">latest_client_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">reply_message</span><span class="p">)</span>
<span class="c1"># Incoming messages are sent to each message</span>
<span class="c1"># handler added to the channel, in the order they</span>
<span class="c1"># were added.</span>
<span class="c1"># If any message handler returns True, the message</span>
<span class="c1"># is considered handled and any subsequent</span>
<span class="c1"># handlers are skipped.</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Client Part #########################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># A reference to the server link</span>
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a client</span>
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">&quot;Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes).&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Check if we know a path to the destination</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Destination is not yet known. Requesting path and waiting for announce to arrive...&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="c1"># Recall the server identity</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="c1"># Inform the user that we&#39;ll begin connecting</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Establishing link with server...&quot;</span><span class="p">)</span>
<span class="c1"># When the server identity is known, we set</span>
<span class="c1"># up a destination</span>
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
<span class="n">server_identity</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
<span class="n">APP_NAME</span><span class="p">,</span>
<span class="s2">&quot;channelexample&quot;</span>
<span class="p">)</span>
<span class="c1"># And create a link</span>
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
<span class="c1"># We set a callback that will get executed</span>
<span class="c1"># every time a packet is received over the</span>
<span class="c1"># link</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">client_message_received</span><span class="p">)</span>
<span class="c1"># We&#39;ll also set up functions to inform the</span>
<span class="c1"># user when the link is established or closed</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="c1"># Everything is set up, so let&#39;s enter a loop</span>
<span class="c1"># for the user to interact with the example</span>
<span class="n">client_loop</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
<span class="k">global</span> <span class="n">server_link</span>
<span class="c1"># Wait for the link to become active</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&gt; &quot;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&quot; &quot;</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="c1"># Check if we should quit the example</span>
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;quit&quot;</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;q&quot;</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;exit&quot;</span><span class="p">:</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="c1"># If not, send the entered text over the link</span>
<span class="k">if</span> <span class="n">text</span> <span class="o">!=</span> <span class="s2">&quot;&quot;</span><span class="p">:</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">StringMessage</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">packed_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">pack</span><span class="p">())</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">server_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
<span class="k">if</span> <span class="n">channel</span><span class="o">.</span><span class="n">is_ready_to_send</span><span class="p">():</span>
<span class="k">if</span> <span class="n">packed_size</span> <span class="o">&lt;=</span> <span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">:</span>
<span class="n">channel</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Cannot send this packet, the data size of &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="n">packed_size</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; bytes exceeds the link packet MDU of &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; bytes&quot;</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span>
<span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Channel is not ready to send, please wait for &quot;</span> <span class="o">+</span>
<span class="s2">&quot;pending messages to complete.&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Error while sending data over the link: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="c1"># This function is called when a link</span>
<span class="c1"># has been established with the server</span>
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="c1"># We store a reference to the link</span>
<span class="c1"># instance for later use</span>
<span class="k">global</span> <span class="n">server_link</span>
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="c1"># Register messages and add handler to channel</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">register_message_type</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">add_message_handler</span><span class="p">(</span><span class="n">client_message_received</span><span class="p">)</span>
<span class="c1"># Inform the user that the server is</span>
<span class="c1"># connected</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link established with server, enter some text to send, or </span><span class="se">\&quot;</span><span class="s2">quit</span><span class="se">\&quot;</span><span class="s2"> to quit&quot;</span><span class="p">)</span>
<span class="c1"># When a link is closed, we&#39;ll inform the</span>
<span class="c1"># user, and exit the program</span>
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The link timed out, exiting now&quot;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The link was closed by the server, exiting now&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When a packet is received over the link, we</span>
<span class="c1"># simply print out the data.</span>
<span class="k">def</span> <span class="nf">client_message_received</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringMessage</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received data on the link: &quot;</span> <span class="o">+</span> <span class="n">message</span><span class="o">.</span><span class="n">data</span> <span class="o">+</span> <span class="s2">&quot; (message created at &quot;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot;)&quot;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&gt; &quot;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&quot; &quot;</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Program Startup #####################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># This part of the program runs at startup,</span>
<span class="c1"># and parses input of from the user, and then</span>
<span class="c1"># starts up the desired program mode.</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&quot;Simple link example&quot;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;-s&quot;</span><span class="p">,</span>
<span class="s2">&quot;--server&quot;</span><span class="p">,</span>
<span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;wait for incoming link requests from clients&quot;</span>
<span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;--config&quot;</span><span class="p">,</span>
<span class="n">action</span><span class="o">=</span><span class="s2">&quot;store&quot;</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;path to alternative Reticulum config directory&quot;</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
<span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;destination&quot;</span><span class="p">,</span>
<span class="n">nargs</span><span class="o">=</span><span class="s2">&quot;?&quot;</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;hexadecimal hash of the server destination&quot;</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
<span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py</a>.</p>
</section>
<section id="filetransfer">
<span id="example-filetransfer"></span><h2>Filetransfer<a class="headerlink" href="#filetransfer" title="Permalink to this heading">#</a></h2>
<p>The <em>Filetransfer</em> example implements a basic file-server program that
@ -2543,7 +2947,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>
@ -2578,6 +2982,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<li><a class="reference internal" href="#link">Link</a></li>
<li><a class="reference internal" href="#example-identify">Identification</a></li>
<li><a class="reference internal" href="#requests-responses">Requests &amp; Responses</a></li>
<li><a class="reference internal" href="#channel">Channel</a></li>
<li><a class="reference internal" href="#filetransfer">Filetransfer</a></li>
</ul>
</li>

View File

@ -236,7 +236,7 @@
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -222,13 +222,15 @@
<section class="genindex-section">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox"><a href="#A"><strong>A</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <a href="#I"><strong>I</strong></a> | <a href="#K"><strong>K</strong></a> | <a href="#L"><strong>L</strong></a> | <a href="#M"><strong>M</strong></a> | <a href="#N"><strong>N</strong></a> | <a href="#P"><strong>P</strong></a> | <a href="#R"><strong>R</strong></a> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#V"><strong>V</strong></a></div>
<div class="genindex-jumpbox"><a href="#A"><strong>A</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <a href="#I"><strong>I</strong></a> | <a href="#K"><strong>K</strong></a> | <a href="#L"><strong>L</strong></a> | <a href="#M"><strong>M</strong></a> | <a href="#N"><strong>N</strong></a> | <a href="#P"><strong>P</strong></a> | <a href="#R"><strong>R</strong></a> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#U"><strong>U</strong></a> | <a href="#V"><strong>V</strong></a></div>
</section>
<section id="A" class="genindex-section">
<h2>A</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.accepts_links">accepts_links() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Channel.Channel.add_message_handler">add_message_handler() (RNS.Channel.Channel method)</a>
</li>
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
</li>
@ -249,6 +251,8 @@
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Resource.cancel">cancel() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Channel.Channel">Channel (class in RNS.Channel)</a>
</li>
<li><a href="reference.html#RNS.Destination.clear_default_app_data">clear_default_app_data() (RNS.Destination method)</a>
</li>
@ -330,6 +334,8 @@
<h2>G</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.get_channel">get_channel() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_data_size">get_data_size() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_establishment_rate">get_establishment_rate() (RNS.Link method)</a>
@ -411,6 +417,8 @@
<li><a href="reference.html#RNS.Link.inactive_for">inactive_for() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Resource.is_compressed">is_compressed() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Channel.Channel.is_ready_to_send">is_ready_to_send() (RNS.Channel.Channel method)</a>
</li>
</ul></td>
</tr></table>
@ -456,6 +464,14 @@
<h2>M</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Channel.Channel.MDU">MDU (RNS.Channel.Channel property)</a>
</li>
<li><a href="reference.html#RNS.MessageBase">MessageBase (class in RNS)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.MessageBase.MSGTYPE">MSGTYPE (RNS.MessageBase attribute)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.MTU">MTU (RNS.Reticulum attribute)</a>
</li>
</ul></td>
@ -484,12 +500,14 @@
<h2>P</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
<li><a href="reference.html#RNS.MessageBase.pack">pack() (RNS.MessageBase method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
</li>
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
@ -507,13 +525,17 @@
<li><a href="reference.html#RNS.Identity.recall_app_data">recall_app_data() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Transport.register_announce_handler">register_announce_handler() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.Channel.Channel.register_message_type">register_message_type() (RNS.Channel.Channel method)</a>
</li>
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
<li><a href="reference.html#RNS.Channel.Channel.remove_message_handler">remove_message_handler() (RNS.Channel.Channel method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Transport.request_path">request_path() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt">RequestReceipt (class in RNS)</a>
@ -532,8 +554,12 @@
<h2>S</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet.send">send() (RNS.Packet method)</a>
<li><a href="reference.html#RNS.Channel.Channel.send">send() (RNS.Channel.Channel method)</a>
<ul>
<li><a href="reference.html#RNS.Packet.send">(RNS.Packet method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Destination.set_default_app_data">set_default_app_data() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_delivery_callback">set_delivery_callback() (RNS.PacketReceipt method)</a>
@ -606,6 +632,16 @@
</tr></table>
</section>
<section id="U" class="genindex-section">
<h2>U</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.MessageBase.unpack">unpack() (RNS.MessageBase method)</a>
</li>
</ul></td>
</tr></table>
</section>
<section id="V" class="genindex-section">
<h2>V</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
@ -628,7 +664,7 @@
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -253,6 +253,12 @@ links, such as local WiFi, wired Ethernet, the Internet, or any combination.</p>
transceivers or infrastructure just to try it out. Launching the programs on separate
devices connected to the same WiFi network is enough to get started, and physical
radio interfaces can then be added later.</p>
<section id="remote-shell">
<h3>Remote Shell<a class="headerlink" href="#remote-shell" title="Permalink to this heading">#</a></h3>
<p>The <a class="reference external" href="https://github.com/acehoss/rnsh">rnsh</a> program lets you establish fully interactive
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
remote system, and is similar to how <code class="docutils literal notranslate"><span class="pre">ssh</span></code> works.</p>
</section>
<section id="nomad-network">
<h3>Nomad Network<a class="headerlink" href="#nomad-network" title="Permalink to this heading">#</a></h3>
<p>The terminal-based program <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>
@ -604,7 +610,7 @@ section of this manual.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>
@ -634,6 +640,7 @@ section of this manual.</p>
<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="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
<li><a class="reference internal" href="#sideband">Sideband</a></li>
</ul>

View File

@ -442,7 +442,7 @@ connectivity for client devices.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -226,6 +226,7 @@
<p>This manual aims to provide you with all the information you need to
understand Reticulum, build networks or develop programs using it, or
to participate in the development of Reticulum itself.</p>
<p>This manual is also available in <a class="reference external" href="https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf">PDF</a> and <a class="reference external" href="https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub">EPUB</a> formats.</p>
<section id="table-of-contents">
<h2>Table Of Contents<a class="headerlink" href="#table-of-contents" title="Permalink to this heading">#</a></h2>
</section>
@ -242,6 +243,7 @@ to participate in the development of Reticulum itself.</p>
<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#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#sideband">Sideband</a></li>
</ul>
@ -353,6 +355,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="examples.html#link">Link</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#example-identify">Identification</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#requests-responses">Requests &amp; Responses</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#channel">Channel</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
</ul>
</li>
@ -375,6 +378,8 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Link"><code class="docutils literal notranslate"><span class="pre">Link</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RequestReceipt"><code class="docutils literal notranslate"><span class="pre">RequestReceipt</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Resource"><code class="docutils literal notranslate"><span class="pre">Resource</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Channel.Channel"><code class="docutils literal notranslate"><span class="pre">Channel</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a></li>
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Transport"><code class="docutils literal notranslate"><span class="pre">Transport</span></code></a></li>
</ul>
</li>
@ -408,7 +413,7 @@ to participate in the development of Reticulum itself.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -954,7 +954,7 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -421,7 +421,7 @@ connected outliers are now an integral part of the network.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

Binary file not shown.

View File

@ -1114,6 +1114,17 @@ thus preserved. This method can be used for authentication.</p>
be used if a new link to the same destination is established.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_channel">
<span class="sig-name descname"><span class="pre">get_channel</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_channel" title="Permalink to this definition">#</a></dt>
<dd><p>Get the <code class="docutils literal notranslate"><span class="pre">Channel</span></code> for this link.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> object</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_link_closed_callback">
<span class="sig-name descname"><span class="pre">set_link_closed_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_link_closed_callback" title="Permalink to this definition">#</a></dt>
@ -1367,6 +1378,153 @@ the resource advertisement it will begin transferring.</p>
</dd></dl>
<p id="api-channel"><h3> Channel </h3></p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Channel.Channel">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.Channel.</span></span><span class="sig-name descname"><span class="pre">Channel</span></span><a class="headerlink" href="#RNS.Channel.Channel" title="Permalink to this definition">#</a></dt>
<dd><p>Provides reliable delivery of messages over
a link.</p>
<p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> differs from <code class="docutils literal notranslate"><span class="pre">Request</span></code> and
<code class="docutils literal notranslate"><span class="pre">Resource</span></code> in some important ways:</p>
<blockquote>
<div><dl class="simple">
<dt><strong>Continuous</strong></dt><dd><p>Messages can be sent or received as long as
the <code class="docutils literal notranslate"><span class="pre">Link</span></code> is open.</p>
</dd>
<dt><strong>Bi-directional</strong></dt><dd><p>Messages can be sent in either direction on
the <code class="docutils literal notranslate"><span class="pre">Link</span></code>; neither end is the client or
server.</p>
</dd>
<dt><strong>Size-constrained</strong></dt><dd><p>Messages must be encoded into a single packet.</p>
</dd>
</dl>
</div></blockquote>
<p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> is similar to <code class="docutils literal notranslate"><span class="pre">Packet</span></code>, except that it
provides reliable delivery (automatic retries) as well
as a structure for exchanging several types of
messages over the <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
<p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> is not instantiated directly, but rather
obtained from a <code class="docutils literal notranslate"><span class="pre">Link</span></code> with <code class="docutils literal notranslate"><span class="pre">get_channel()</span></code>.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Channel.Channel.register_message_type">
<span class="sig-name descname"><span class="pre">register_message_type</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">message_class</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Type</span><span class="p"><span class="pre">[</span></span><a class="reference internal" href="#RNS.MessageBase" title="RNS.Channel.MessageBase"><span class="pre">MessageBase</span></a><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Channel.Channel.register_message_type" title="Permalink to this definition">#</a></dt>
<dd><p>Register a message class for reception over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>.</p>
<p>Message classes must extend <code class="docutils literal notranslate"><span class="pre">MessageBase</span></code>.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>message_class</strong> Class to register</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Channel.Channel.add_message_handler">
<span class="sig-name descname"><span class="pre">add_message_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">MessageCallbackType</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Channel.Channel.add_message_handler" title="Permalink to this definition">#</a></dt>
<dd><p>Add a handler for incoming messages. A handler
has the following signature:</p>
<p><code class="docutils literal notranslate"><span class="pre">(message:</span> <span class="pre">MessageBase)</span> <span class="pre">-&gt;</span> <span class="pre">bool</span></code></p>
<p>Handlers are processed in the order they are
added. If any handler returns True, processing
of the message stops; handlers after the
returning handler will not be called.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> Function to call</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Channel.Channel.remove_message_handler">
<span class="sig-name descname"><span class="pre">remove_message_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">MessageCallbackType</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Channel.Channel.remove_message_handler" title="Permalink to this definition">#</a></dt>
<dd><p>Remove a handler added with <code class="docutils literal notranslate"><span class="pre">add_message_handler</span></code>.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> handler to remove</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Channel.Channel.is_ready_to_send">
<span class="sig-name descname"><span class="pre">is_ready_to_send</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="headerlink" href="#RNS.Channel.Channel.is_ready_to_send" title="Permalink to this definition">#</a></dt>
<dd><p>Check if <code class="docutils literal notranslate"><span class="pre">Channel</span></code> is ready to send.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>True if ready</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Channel.Channel.send">
<span class="sig-name descname"><span class="pre">send</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">message</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.MessageBase" title="RNS.Channel.MessageBase"><span class="pre">MessageBase</span></a></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Envelope</span></span></span><a class="headerlink" href="#RNS.Channel.Channel.send" title="Permalink to this definition">#</a></dt>
<dd><p>Send a message. If a message send is attempted and
<code class="docutils literal notranslate"><span class="pre">Channel</span></code> is not ready, an exception is thrown.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>message</strong> an instance of a <code class="docutils literal notranslate"><span class="pre">MessageBase</span></code> subclass</p>
</dd>
</dl>
</dd></dl>
<dl class="py property">
<dt class="sig sig-object py" id="RNS.Channel.Channel.MDU">
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">MDU</span></span><a class="headerlink" href="#RNS.Channel.Channel.MDU" title="Permalink to this definition">#</a></dt>
<dd><p>Maximum Data Unit: the number of bytes available
for a message to consume in a single send. This
value is adjusted from the <code class="docutils literal notranslate"><span class="pre">Link</span></code> MDU to accommodate
message header information.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>number of bytes available</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
<p id="api-messsagebase"><h3> MessageBase </h3></p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.MessageBase">
<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">MessageBase</span></span><a class="headerlink" href="#RNS.MessageBase" title="Permalink to this definition">#</a></dt>
<dd><p>Base type for any messages sent or received on a Channel.
Subclasses must define the two abstract methods as well as
the <code class="docutils literal notranslate"><span class="pre">MSGTYPE</span></code> class variable.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.MessageBase.MSGTYPE">
<span class="sig-name descname"><span class="pre">MSGTYPE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">None</span></em><a class="headerlink" href="#RNS.MessageBase.MSGTYPE" title="Permalink to this definition">#</a></dt>
<dd><p>Defines a unique identifier for a message class.</p>
<ul class="simple">
<li><p>Must be unique within all classes registered with a <code class="docutils literal notranslate"><span class="pre">Channel</span></code></p></li>
<li><p>Must be less than <code class="docutils literal notranslate"><span class="pre">0xf000</span></code>. Values greater than or equal to <code class="docutils literal notranslate"><span class="pre">0xf000</span></code> are reserved.</p></li>
</ul>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.MessageBase.pack">
<em class="property"><span class="pre">abstract</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">pack</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">bytes</span></span></span><a class="headerlink" href="#RNS.MessageBase.pack" title="Permalink to this definition">#</a></dt>
<dd><p>Create and return the binary representation of the message</p>
<dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>binary representation of message</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.MessageBase.unpack">
<em class="property"><span class="pre">abstract</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">unpack</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">raw</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.MessageBase.unpack" title="Permalink to this definition">#</a></dt>
<dd><p>Populate message from binary representation</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>raw</strong> binary representation</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
<p id="api-transport"><h3> Transport </h3></p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Transport">
@ -1494,7 +1652,7 @@ will announce it.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>
@ -1604,6 +1762,7 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Link.inactive_for"><code class="docutils literal notranslate"><span class="pre">inactive_for()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_remote_identity"><code class="docutils literal notranslate"><span class="pre">get_remote_identity()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.teardown"><code class="docutils literal notranslate"><span class="pre">teardown()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.get_channel"><code class="docutils literal notranslate"><span class="pre">get_channel()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.set_link_closed_callback"><code class="docutils literal notranslate"><span class="pre">set_link_closed_callback()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.set_packet_callback"><code class="docutils literal notranslate"><span class="pre">set_packet_callback()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Link.set_resource_callback"><code class="docutils literal notranslate"><span class="pre">set_resource_callback()</span></code></a></li>
@ -1633,6 +1792,21 @@ will announce it.</p>
<li><a class="reference internal" href="#RNS.Resource.is_compressed"><code class="docutils literal notranslate"><span class="pre">is_compressed()</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Channel.Channel"><code class="docutils literal notranslate"><span class="pre">Channel</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Channel.Channel.register_message_type"><code class="docutils literal notranslate"><span class="pre">register_message_type()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.add_message_handler"><code class="docutils literal notranslate"><span class="pre">add_message_handler()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.remove_message_handler"><code class="docutils literal notranslate"><span class="pre">remove_message_handler()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.is_ready_to_send"><code class="docutils literal notranslate"><span class="pre">is_ready_to_send()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.send"><code class="docutils literal notranslate"><span class="pre">send()</span></code></a></li>
<li><a class="reference internal" href="#RNS.Channel.Channel.MDU"><code class="docutils literal notranslate"><span class="pre">MDU</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a><ul>
<li><a class="reference internal" href="#RNS.MessageBase.MSGTYPE"><code class="docutils literal notranslate"><span class="pre">MSGTYPE</span></code></a></li>
<li><a class="reference internal" href="#RNS.MessageBase.pack"><code class="docutils literal notranslate"><span class="pre">pack()</span></code></a></li>
<li><a class="reference internal" href="#RNS.MessageBase.unpack"><code class="docutils literal notranslate"><span class="pre">unpack()</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#RNS.Transport"><code class="docutils literal notranslate"><span class="pre">Transport</span></code></a><ul>
<li><a class="reference internal" href="#RNS.Transport.PATHFINDER_M"><code class="docutils literal notranslate"><span class="pre">PATHFINDER_M</span></code></a></li>
<li><a class="reference internal" href="#RNS.Transport.register_announce_handler"><code class="docutils literal notranslate"><span class="pre">register_announce_handler()</span></code></a></li>

View File

@ -241,7 +241,7 @@
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

File diff suppressed because one or more lines are too long

View File

@ -288,7 +288,7 @@ report issues, suggest functionality and contribute code to Reticulum.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -1126,7 +1126,7 @@ those risks are acceptable to you.</p>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -757,7 +757,7 @@ WantedBy=multi-user.target
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -390,7 +390,7 @@ want to help out with this, or can help sponsor an audit, please do get in touch
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2022, Mark Qvist
Copyright &#169; 2023, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>

View File

@ -10,7 +10,7 @@ sys.path.insert(0, os.path.abspath('../..'))
# -- Project information -----------------------------------------------------
project = 'Reticulum Network Stack'
copyright = '2022, Mark Qvist'
copyright = '2023, Mark Qvist'
author = 'Mark Qvist'
# The full version, including alpha/beta/rc tags

View File

@ -92,6 +92,18 @@ The *Request* example explores sendig requests and receiving responses.
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
.. _example-channel:
Channel
=======
The *Channel* example explores using a ``Channel`` to send structured
data between peers of a ``Link``.
.. literalinclude:: ../../Examples/Channel.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py>`_.
.. _example-filetransfer:
Filetransfer

View File

@ -42,6 +42,13 @@ transceivers or infrastructure just to try it out. Launching the programs on sep
devices connected to the same WiFi network is enough to get started, and physical
radio interfaces can then be added later.
Remote Shell
^^^^^^^^^^^^
The `rnsh <https://github.com/acehoss/rnsh>`_ program lets you establish fully interactive
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
remote system, and is similar to how ``ssh`` works.
Nomad Network
^^^^^^^^^^^^^

View File

@ -5,6 +5,10 @@ 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
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
Table Of Contents

View File

@ -121,6 +121,34 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
:members:
.. _api-channel:
.. only:: html
|start-h3| Channel |end-h3|
.. only:: latex
Channel
-------
.. autoclass:: RNS.Channel.Channel()
:members:
.. _api-messsagebase:
.. only:: html
|start-h3| MessageBase |end-h3|
.. only:: latex
MessageBase
-----------
.. autoclass:: RNS.MessageBase()
:members:
.. _api-transport:
.. only:: html

View File

@ -4,6 +4,7 @@ from .hashes import TestSHA256
from .hashes import TestSHA512
from .identity import TestIdentity
from .link import TestLink
from .channel import TestChannel
if __name__ == '__main__':
unittest.main(verbosity=2)

375
tests/channel.py Normal file
View File

@ -0,0 +1,375 @@
from __future__ import annotations
import threading
import RNS
from RNS.Channel import MessageState, ChannelOutletBase, Channel, MessageBase
from RNS.vendor import umsgpack
from typing import Callable
import contextlib
import typing
import types
import time
import uuid
import unittest
class Packet:
timeout = 1.0
def __init__(self, raw: bytes):
self.state = MessageState.MSGSTATE_NEW
self.raw = raw
self.packet_id = uuid.uuid4()
self.tries = 0
self.timeout_id = None
self.lock = threading.RLock()
self.instances = 0
self.timeout_callback: Callable[[Packet], None] | None = None
self.delivered_callback: Callable[[Packet], None] | None = None
def set_timeout(self, callback: Callable[[Packet], None] | None, timeout: float):
with self.lock:
if timeout is not None:
self.timeout = timeout
self.timeout_callback = callback
def send(self):
self.tries += 1
self.state = MessageState.MSGSTATE_SENT
def elapsed(timeout: float, timeout_id: uuid.uuid4):
with self.lock:
self.instances += 1
try:
time.sleep(timeout)
with self.lock:
if self.timeout_id == timeout_id:
self.timeout_id = None
self.state = MessageState.MSGSTATE_FAILED
if self.timeout_callback:
self.timeout_callback(self)
finally:
with self.lock:
self.instances -= 1
self.timeout_id = uuid.uuid4()
threading.Thread(target=elapsed, name="Packet Timeout", args=[self.timeout, self.timeout_id],
daemon=True).start()
def clear_timeout(self):
self.timeout_id = None
def set_delivered_callback(self, callback: Callable[[Packet], None]):
self.delivered_callback = callback
def delivered(self):
with self.lock:
self.state = MessageState.MSGSTATE_DELIVERED
self.timeout_id = None
if self.delivered_callback:
self.delivered_callback(self)
class ChannelOutletTest(ChannelOutletBase):
def get_packet_state(self, packet: Packet) -> MessageState:
return packet.state
def set_packet_timeout_callback(self, packet: Packet, callback: Callable[[Packet], None] | None,
timeout: float | None = None):
packet.set_timeout(callback, timeout)
def set_packet_delivered_callback(self, packet: Packet, callback: Callable[[Packet], None] | None):
packet.set_delivered_callback(callback)
def get_packet_id(self, packet: Packet) -> any:
return packet.packet_id
def __init__(self, mdu: int, rtt: float):
self.link_id = uuid.uuid4()
self.timeout_callbacks = 0
self._mdu = mdu
self._rtt = rtt
self._usable = True
self.packets = []
self.packet_callback: Callable[[ChannelOutletBase, bytes], None] | None = None
def send(self, raw: bytes) -> Packet:
packet = Packet(raw)
packet.send()
self.packets.append(packet)
return packet
def resend(self, packet: Packet) -> Packet:
packet.send()
return packet
@property
def mdu(self):
return self._mdu
@property
def rtt(self):
return self._rtt
@property
def is_usable(self):
return self._usable
def timed_out(self):
self.timeout_callbacks += 1
def __str__(self):
return str(self.link_id)
class MessageTest(MessageBase):
MSGTYPE = 0xabcd
def __init__(self):
self.id = str(uuid.uuid4())
self.data = "test"
self.not_serialized = str(uuid.uuid4())
def pack(self) -> bytes:
return umsgpack.packb((self.id, self.data))
def unpack(self, raw):
self.id, self.data = umsgpack.unpackb(raw)
class SystemMessage(MessageBase):
MSGTYPE = 0xf000
def pack(self) -> bytes:
return bytes()
def unpack(self, raw):
pass
class ProtocolHarness(contextlib.AbstractContextManager):
def __init__(self, rtt: float):
self.outlet = ChannelOutletTest(mdu=500, rtt=rtt)
self.channel = Channel(self.outlet)
def cleanup(self):
self.channel._shutdown()
def __exit__(self, __exc_type: typing.Type[BaseException], __exc_value: BaseException,
__traceback: types.TracebackType) -> bool:
# self._log.debug(f"__exit__({__exc_type}, {__exc_value}, {__traceback})")
self.cleanup()
return False
class TestChannel(unittest.TestCase):
def setUp(self) -> None:
print("")
self.rtt = 0.001
self.retry_interval = self.rtt * 150
Packet.timeout = self.retry_interval
self.h = ProtocolHarness(self.rtt)
def tearDown(self) -> None:
self.h.cleanup()
def test_send_one_retry(self):
print("Channel test one retry")
message = MessageTest()
self.assertEqual(0, len(self.h.outlet.packets))
envelope = self.h.channel.send(message)
self.assertIsNotNone(envelope)
self.assertIsNotNone(envelope.raw)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertIsNotNone(envelope.packet)
self.assertTrue(envelope in self.h.channel._tx_ring)
self.assertTrue(envelope.tracked)
packet = self.h.outlet.packets[0]
self.assertEqual(envelope.packet, packet)
self.assertEqual(1, envelope.tries)
self.assertEqual(1, packet.tries)
self.assertEqual(1, packet.instances)
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
self.assertEqual(envelope.raw, packet.raw)
time.sleep(self.retry_interval * 1.5)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertEqual(2, envelope.tries)
self.assertEqual(2, packet.tries)
self.assertEqual(1, packet.instances)
time.sleep(self.retry_interval)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertEqual(self.h.outlet.packets[0], packet)
self.assertEqual(3, envelope.tries)
self.assertEqual(3, packet.tries)
self.assertEqual(1, packet.instances)
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
packet.delivered()
self.assertEqual(MessageState.MSGSTATE_DELIVERED, packet.state)
time.sleep(self.retry_interval)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertEqual(3, envelope.tries)
self.assertEqual(3, packet.tries)
self.assertEqual(0, packet.instances)
self.assertFalse(envelope.tracked)
def test_send_timeout(self):
print("Channel test retry count exceeded")
message = MessageTest()
self.assertEqual(0, len(self.h.outlet.packets))
envelope = self.h.channel.send(message)
self.assertIsNotNone(envelope)
self.assertIsNotNone(envelope.raw)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertIsNotNone(envelope.packet)
self.assertTrue(envelope in self.h.channel._tx_ring)
self.assertTrue(envelope.tracked)
packet = self.h.outlet.packets[0]
self.assertEqual(envelope.packet, packet)
self.assertEqual(1, envelope.tries)
self.assertEqual(1, packet.tries)
self.assertEqual(1, packet.instances)
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
self.assertEqual(envelope.raw, packet.raw)
time.sleep(self.retry_interval * 7.5)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertEqual(5, envelope.tries)
self.assertEqual(5, packet.tries)
self.assertEqual(0, packet.instances)
self.assertEqual(MessageState.MSGSTATE_FAILED, packet.state)
self.assertFalse(envelope.tracked)
def test_multiple_handler(self):
print("Channel test multiple handler short circuit")
handler1_called = 0
handler1_return = True
handler2_called = 0
def handler1(msg: MessageBase):
nonlocal handler1_called, handler1_return
self.assertIsInstance(msg, MessageTest)
handler1_called += 1
return handler1_return
def handler2(msg: MessageBase):
nonlocal handler2_called
self.assertIsInstance(msg, MessageTest)
handler2_called += 1
message = MessageTest()
self.h.channel.register_message_type(MessageTest)
self.h.channel.add_message_handler(handler1)
self.h.channel.add_message_handler(handler2)
envelope = RNS.Channel.Envelope(self.h.outlet, message, sequence=0)
raw = envelope.pack()
self.h.channel._receive(raw)
self.assertEqual(1, handler1_called)
self.assertEqual(0, handler2_called)
handler1_return = False
envelope = RNS.Channel.Envelope(self.h.outlet, message, sequence=1)
raw = envelope.pack()
self.h.channel._receive(raw)
self.assertEqual(2, handler1_called)
self.assertEqual(1, handler2_called)
def test_system_message_check(self):
print("Channel test register system message")
with self.assertRaises(RNS.Channel.ChannelException):
self.h.channel.register_message_type(SystemMessage)
self.h.channel._register_message_type(SystemMessage, is_system_type=True)
def eat_own_dog_food(self, message: MessageBase, checker: typing.Callable[[MessageBase], None]):
decoded: [MessageBase] = []
def handle_message(message: MessageBase):
decoded.append(message)
self.h.channel.register_message_type(message.__class__)
self.h.channel.add_message_handler(handle_message)
self.assertEqual(len(self.h.outlet.packets), 0)
envelope = self.h.channel.send(message)
time.sleep(self.retry_interval * 0.5)
self.assertIsNotNone(envelope)
self.assertIsNotNone(envelope.raw)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertIsNotNone(envelope.packet)
self.assertTrue(envelope in self.h.channel._tx_ring)
self.assertTrue(envelope.tracked)
packet = self.h.outlet.packets[0]
self.assertEqual(envelope.packet, packet)
self.assertEqual(1, envelope.tries)
self.assertEqual(1, packet.tries)
self.assertEqual(1, packet.instances)
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
self.assertEqual(envelope.raw, packet.raw)
packet.delivered()
self.assertEqual(MessageState.MSGSTATE_DELIVERED, packet.state)
time.sleep(self.retry_interval * 2)
self.assertEqual(1, len(self.h.outlet.packets))
self.assertEqual(1, envelope.tries)
self.assertEqual(1, packet.tries)
self.assertEqual(0, packet.instances)
self.assertFalse(envelope.tracked)
self.assertEqual(len(self.h.outlet.packets), 1)
self.assertEqual(MessageState.MSGSTATE_DELIVERED, packet.state)
self.assertFalse(envelope.tracked)
self.assertEqual(0, len(decoded))
self.h.channel._receive(packet.raw)
self.assertEqual(1, len(decoded))
rx_message = decoded[0]
self.assertIsNotNone(rx_message)
self.assertIsInstance(rx_message, message.__class__)
checker(rx_message)
def test_send_receive_message_test(self):
print("Channel test send and receive message")
message = MessageTest()
def check(rx_message: MessageBase):
self.assertIsInstance(rx_message, message.__class__)
self.assertEqual(message.id, rx_message.id)
self.assertEqual(message.data, rx_message.data)
self.assertNotEqual(message.not_serialized, rx_message.not_serialized)
self.eat_own_dog_food(message, check)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@ -6,6 +6,8 @@ import threading
import time
import RNS
import os
from tests.channel import MessageTest
from RNS.Channel import MessageBase
APP_NAME = "rns_unit_tests"
@ -346,6 +348,54 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
def test_10_channel_round_trip(self):
global c_rns
init_rns(self)
print("")
print("Channel round trip test")
# TODO: Load this from public bytes only
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
l1 = RNS.Link(dest)
time.sleep(1)
self.assertEqual(l1.status, RNS.Link.ACTIVE)
received = []
def handle_message(message: MessageBase):
received.append(message)
test_message = MessageTest()
test_message.data = "Hello"
channel = l1.get_channel()
channel.register_message_type(MessageTest)
channel.add_message_handler(handle_message)
channel.send(test_message)
time.sleep(0.5)
self.assertEqual(1, len(received))
rx_message = received[0]
self.assertIsInstance(rx_message, MessageTest)
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))
l1.teardown()
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
self.assertEqual(0, len(l1._channel._rx_ring))
def size_str(self, num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
@ -404,6 +454,13 @@ def targets(yp=False):
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
link.set_resource_started_callback(resource_started)
link.set_resource_concluded_callback(resource_concluded)
channel = link.get_channel()
def handle_message(message):
message.data = message.data + " back"
channel.send(message)
channel.register_message_type(MessageTest)
channel.add_message_handler(handle_message)
m_rns = RNS.Reticulum("./tests/rnsconfig")
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))