mirror of
https://github.com/markqvist/Reticulum.git
synced 2025-05-20 07:10:56 -04:00
Compare commits
No commits in common. "master" and "0.9.4" have entirely different histories.
97 changed files with 1141 additions and 2679 deletions
.github
Changelog.mdContributing.mdExamples
FUNDING.ymlLICENSEREADME.mdRNS
Buffer.pyChannel.py
Roadmap.mdCryptography
Destination.pyIdentity.pyInterfaces
AX25KISSInterface.py
Link.pyPacket.pyResolver.pyResource.pyReticulum.pyTransport.pyAndroid
AutoInterface.pyBackboneInterface.pyI2PInterface.pyInterface.pyKISSInterface.pyLocalInterface.pyPipeInterface.pyRNodeInterface.pyRNodeMultiInterface.pySerialInterface.pyTCPInterface.pyUDPInterface.py__init__.pyutil
Utilities
__init__.py_version.pyvendor
docs
Reticulum Manual.epubReticulum Manual.pdf
setup.pymanual
.buildinfo
_sources
_static
examples.htmlforhumans.htmlgenindex.htmlgettingstartedfast.htmlhardware.htmlindex.htmlinterfaces.htmlnetworks.htmlobjects.invreference.htmlsearch.htmlsearchindex.jssupport.htmlunderstanding.htmlusing.htmlwhatis.htmlsource
tests
1
.github/ISSUE_TEMPLATE/🐛-bug-report.md
vendored
1
.github/ISSUE_TEMPLATE/🐛-bug-report.md
vendored
|
@ -12,7 +12,6 @@ Before creating a bug report on this issue tracker, you **must** read the [Contr
|
|||
|
||||
- The issue tracker is used by developers of this project. **Do not use it to ask general questions, or for support requests**.
|
||||
- Ideas and feature requests can be made on the [Discussions](https://github.com/markqvist/Reticulum/discussions). **Only** feature requests accepted by maintainers and developers are tracked and included on the issue tracker. **Do not post feature requests here**.
|
||||
- Do not submit code written using large language models (LLMs) or other generative 'AI' programs (see the [Generative AI Policy](/Contributing.md#generative-ai-policy) for details).
|
||||
- After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), **delete this section only** (*"Read the Contribution Guidelines"*) from your bug report, **and fill in all the other sections**.
|
||||
|
||||
**Describe the Bug**
|
||||
|
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -29,9 +29,7 @@ jobs:
|
|||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: |
|
||||
python -m pip install -q cryptography
|
||||
make test
|
||||
- run: make test
|
||||
|
||||
package:
|
||||
needs: test
|
||||
|
|
85
Changelog.md
85
Changelog.md
|
@ -1,88 +1,3 @@
|
|||
### 2025-05-15: RNS β 0.9.6
|
||||
|
||||
This release activates AES-256 as the default encryption mode for all communication. It is the last release that will support the old AES-128 based modes, which will be entirely phased out in the next release.
|
||||
|
||||
This release also includes a number of API and resource consumption improvements, and fixes a bug.
|
||||
|
||||
**Changes**
|
||||
- Enabled AES-256 as default encryption mode for all traffic
|
||||
- Added dynamic link keepalive and timeout calculation
|
||||
- Added ability to efficiently transfer files as responses in the `Request` API
|
||||
- Added ability to include metadata on `Resource` transfers
|
||||
- Added option to specify `Resource` auto-compression limits
|
||||
- Added option to specify `Request` response auto-compression limits
|
||||
- Added `Resource` transfer example
|
||||
- Added allow overwrite option to `rncp`
|
||||
- Improved hardware MTU auto-configuration
|
||||
- Improved handling of file transfers using the `Resource` API
|
||||
- Improved `Resource` transfer memory consumption
|
||||
- Improved memory consumption of applications connected to a shared instance
|
||||
- Improved `rncp` memory consumption for large files
|
||||
- Fixed announce handlers not triggering after shared instance disappearance
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
a23c64a04c1e83fd0ab449f564ac904da7fd4f61c0faf68a063f486cc48b44bd rns-0.9.6-py3-none-any.whl
|
||||
4544882dea902b18b00d8a04c9ab93201974573b7b63c3db06cb310b0acec240 rnspure-0.9.6-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2025-05-09: RNS β 0.9.5
|
||||
|
||||
This release initiates migration of Reticulum from AES-128 to AES-256 as the default link and packet cipher mode. It is a compatibility/migration release, that while supporting AES-256 doesn't use it by default. It will work with both the old AES-128 based modes, and the new AES-256 based modes. There's a very slight penalty in performance to support both the old and new modes at the same time, but only for single packet APIs (not links), and it really shouldn't be noticeable in any everyday use.
|
||||
|
||||
In the next release, version `0.9.6`, Reticulum will transition fully to AES-256 and use it by default for all communications. That means that both single packets and links will use AES-256 by default. The old AES-128 link mode may or may not be available for a few releases, but will ultimately be phased out entirely.
|
||||
|
||||
The update requires no intervention, configuration changes or anything similar from a users or developers perspective. Everything should simply work. This goes both for the `0.9.5` update, and the next `0.9.6` update that transitions fully to AES-256.
|
||||
|
||||
**Changes**
|
||||
- Added support for AES-256 mode to links and packets
|
||||
- Added dynamic link mode support
|
||||
- Added temporary backwards compatibility for AES-128 link and packet modes
|
||||
- Added `get_mode()` method to link API
|
||||
- Added tests for all enabled link modes
|
||||
- Added `instance_name` option and description to default config file
|
||||
- Improved ratchet persist reliability if Reticulum is force killed while persisting ratchets
|
||||
- Fixed interface string representation for some interfaces
|
||||
- Fixed instance name config option being overwritten if option was not last in section
|
||||
- Fixed unhandled potential exception on fast-flapping `BackboneInterface` connections
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
ae6587c86c98cae0df73567af093cc92fe204e71bb01f2506da9aec626a27e97 rns-0.9.5-py3-none-any.whl
|
||||
96208c1d1234e3e4b1c18ca986bad5d4693aeb431453efd7ade33b87f35600e1 rnspure-0.9.5-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2025-04-15: RNS β 0.9.4
|
||||
|
||||
This release significantly improves memory utilisation and performance. It also includes a few new features and general improvements to the included utilities and programs.
|
||||
|
||||
**Changes**
|
||||
- Significantly improved memory utilisation, thread count and performance on nodes with many interfaces or clients
|
||||
- Switched local instance communication to run over abstract domain sockets on Linux and Android
|
||||
- Switched instance IPC to run over abstract domain sockets on Linux and Android
|
||||
- Added kernel event based I/O backend on Linux and Android
|
||||
- Added fast `BackboneInterface` type
|
||||
- Added support for XIAO-ESP32S3 to `rnodeconf`
|
||||
- Added interactive shell option to `rnsd`
|
||||
- Added API option to search for identity by identity hash
|
||||
- Added option to run TCP and Backbone interfaces in AP mode
|
||||
- Improved `RNodeMultiInterface` host communications specification
|
||||
- Improved `rncp` statistics output
|
||||
- Improved link and reverse-table culling
|
||||
- Fixed an occasional I/O thread hang on instance shutdown, that would result in an error printed to the console
|
||||
- Fixed various minor interface logging inconsistencies
|
||||
- Fixed various minor interface checking inconsistencies
|
||||
- Updated internal `configobj` implementation
|
||||
- Refactored various parts of the transport core code
|
||||
- Swicthed to using internal `netinfo` implementation instead of including full `ifaddr` library
|
||||
- Cleaned out unneeded dependencies
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
737294f29e013f9fa9c8c1326006d0547497607156828fee3dc2a0d3ddd754e7 rns-0.9.4-py3-none-any.whl
|
||||
0bd8a908af115c27733484853d779574d6383ebc1d78160e5a72c14ed9692a13 rnspure-0.9.4-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2025-03-13: RNS β 0.9.3
|
||||
|
||||
This maintenance release improves performance and fixes a number of bugs.
|
||||
|
|
|
@ -40,12 +40,4 @@ Pull requests have a high chance of being accepted if they are:
|
|||
|
||||
Even new ideas and proposals that have not been approved by a maintainer, or fall outside the established roadmap, are *occasionally* accepted - if they possess the remaining of the above qualities. If not, they will be closed and removed without comments or explanation.
|
||||
|
||||
## Generative AI Policy
|
||||
|
||||
Contributions written using large language models (LLMs) or other generative 'AI' programs are prohibited. LLMs produce errors so frequently and in a way that is so unlike human error that issues will regularly remain undetected and slip through, even with stringent review. This is not a worthwhile tradeoff for Reticulum, especially considering the limited time maintainers have to correct these issues, and we ask that you refrain from using any such output in your contributions.
|
||||
|
||||
This applies to all official Reticlulm projects and documentation as well as all submitted issues and discussion in official channels, except in cases where language translation and/or speech recogntion technologies are required for communication. We also ask that you avoid using LLMs for troubleshooting, as results can be misleading, and instead request help in one of our [various communities](https://reticulum.network/start.html).
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
By contributing code to this project, you agree that copyright for the code is transferred to the Reticulum maintainers and that the code is irrevocably placed under the [Reticulum License](./LICENSE).
|
||||
By contributing code to this project, you agree that copyright for the code is transferred to the Reticulum maintainers and that the code is irrevocably placed under the [MIT license](./LICENSE).
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# MIT License - Copyright (c) 2024 Mark Qvist / unsigned.io
|
||||
|
||||
# This example illustrates creating a custom interface
|
||||
# definition, that can be loaded and used by Reticulum at
|
||||
# runtime. Any number of custom interfaces can be created
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
##########################################################
|
||||
# This RNS example demonstrates how to perform requests #
|
||||
# and receive responses over a link. #
|
||||
# This RNS example demonstrates how to set perform #
|
||||
# requests and receive responses over a link. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
|
|
|
@ -1,294 +0,0 @@
|
|||
##########################################################
|
||||
# This RNS example demonstrates how to transfer a #
|
||||
# resource over an established link #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
import RNS
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create. Since this echo example
|
||||
# is part of a range of example utilities, we'll put
|
||||
# them all within the app namespace "example_utilities"
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
# 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,
|
||||
"resourceexample"
|
||||
)
|
||||
|
||||
# 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 resources or user input
|
||||
server_loop(server_destination)
|
||||
|
||||
def server_loop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Resource 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
|
||||
RNS.log("Client connected")
|
||||
|
||||
# We configure the link to accept all resources
|
||||
# and set a callback for completed resources
|
||||
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
|
||||
link.set_resource_concluded_callback(resource_concluded)
|
||||
|
||||
link.set_link_closed_callback(client_disconnected)
|
||||
latest_client_link = link
|
||||
|
||||
def client_disconnected(link):
|
||||
RNS.log("Client disconnected")
|
||||
|
||||
def resource_concluded(resource):
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
RNS.log(f"Resource {resource} received")
|
||||
RNS.log(f"Metadata: {resource.metadata}")
|
||||
RNS.log(f"Data length: {os.stat(resource.data.name).st_size}")
|
||||
RNS.log(f"Data can be read directly from: {resource.data}")
|
||||
RNS.log(f"Data can be moved or copied from: {resource.data.name}")
|
||||
RNS.log(f"First 32 bytes of data: {RNS.hexrep(resource.data.read(32))}")
|
||||
else:
|
||||
RNS.log(f"Receiving resource {resource} failed")
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the server link
|
||||
server_link = None
|
||||
|
||||
def random_text_generator():
|
||||
texts = ["They looked up", "On each full moon", "Becky was upset", "I’ll stay away from it", "The pet shop stocks everything"]
|
||||
return texts[random.randint(0, len(texts)-1)]
|
||||
|
||||
# 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")
|
||||
sys.exit(0)
|
||||
|
||||
# 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,
|
||||
"resourceexample"
|
||||
)
|
||||
|
||||
# And create a link
|
||||
link = RNS.Link(server_destination)
|
||||
|
||||
# We'll set up functions to inform the
|
||||
# user when the link is established or closed
|
||||
link.set_link_established_callback(link_established)
|
||||
link.set_link_closed_callback(link_closed)
|
||||
|
||||
# Everything is set up, so let's enter a loop
|
||||
# for the user to interact with the example
|
||||
client_loop()
|
||||
|
||||
def client_loop():
|
||||
global server_link
|
||||
|
||||
# Wait for the link to become active
|
||||
while not server_link:
|
||||
time.sleep(0.1)
|
||||
|
||||
should_quit = False
|
||||
while not should_quit:
|
||||
try:
|
||||
print("> ", end=" ")
|
||||
text = input()
|
||||
|
||||
# Check if we should quit the example
|
||||
if text == "quit" or text == "q" or text == "exit":
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
else:
|
||||
# Generate 32 megabytes of random data
|
||||
data = os.urandom(32*1024*1024)
|
||||
RNS.log(f"Data length: {len(data)}")
|
||||
RNS.log(f"First 32 bytes of data: {RNS.hexrep(data[:32])}")
|
||||
|
||||
# Generate some metadata
|
||||
metadata = {"text": random_text_generator(), "numbers": [1,2,3,4], "blob": os.urandom(16)}
|
||||
|
||||
# Send the resource
|
||||
resource = RNS.Resource(data, server_link, metadata=metadata, callback=resource_concluded_sending, auto_compress=False)
|
||||
|
||||
# Alternatively, you can stream data
|
||||
# directly from an open file descriptor
|
||||
|
||||
# with open("/path/to/file", "rb") as data_file:
|
||||
# resource = RNS.Resource(data_file, server_link, metadata=metadata, callback=resource_concluded_sending, auto_compress=False)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while sending resource over the link: "+str(e))
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
def resource_concluded_sending(resource):
|
||||
if resource.status == RNS.Resource.COMPLETE: RNS.log(f"The resource {resource} was sent successfully")
|
||||
else: RNS.log(f"Sending the resource {resource} failed")
|
||||
|
||||
# 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
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server, hit enter to sand a resource, or type in \"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")
|
||||
|
||||
time.sleep(1.5)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
##########################################################
|
||||
#### 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 resource example")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming resources 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("")
|
||||
sys.exit(0)
|
|
@ -1,3 +1,2 @@
|
|||
liberapay: Reticulum
|
||||
ko_fi: markqvist
|
||||
custom: "https://unsigned.io/donate"
|
16
LICENSE
16
LICENSE
|
@ -1,6 +1,6 @@
|
|||
Reticulum License
|
||||
MIT License, unless otherwise noted
|
||||
|
||||
Copyright (c) 2016-2025 Mark Qvist
|
||||
Copyright (c) 2016-2024 Mark Qvist / unsigned.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@ 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 Software shall not be used in any kind of system which includes amongst
|
||||
its functions the ability to purposefully do harm to human beings.
|
||||
|
||||
- The Software shall not be used, directly or indirectly, in the creation of
|
||||
an artificial intelligence, machine learning or language model training
|
||||
dataset, including but not limited to any use that contributes to the
|
||||
training or development of such a model or algorithm.
|
||||
|
||||
- The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
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,
|
||||
|
|
53
README.md
53
README.md
|
@ -52,7 +52,7 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
|
|||
- Forward Secrecy is available for all communication types, both for single packets and over links
|
||||
- Reticulum uses the following format for encrypted tokens:
|
||||
- Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
|
||||
- AES-256 in CBC mode with PKCS7 padding
|
||||
- AES-128 in CBC mode with PKCS7 padding
|
||||
- HMAC using SHA256 for authentication
|
||||
- IVs are generated through os.urandom()
|
||||
- Unforgeable packet delivery confirmations
|
||||
|
@ -62,7 +62,7 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
|
|||
- Easily create your own custom interfaces for communicating over anything
|
||||
- Authentication and virtual network segmentation on all supported interface types
|
||||
- An intuitive and easy-to-use API
|
||||
- Simpler and easier to use than sockets APIs, but more powerful
|
||||
- Simpler and easier to use than sockets APIs and simpler, but more powerful
|
||||
- Makes building distributed and decentralised applications much simpler
|
||||
- Reliable and efficient transfer of arbitrary amounts of data
|
||||
- Reticulum can handle a few bytes of data or files of many gigabytes
|
||||
|
@ -86,10 +86,9 @@ following resources.
|
|||
|
||||
- You can use the [rnsh](https://github.com/acehoss/rnsh) program to establish remote shell sessions over Reticulum.
|
||||
- [LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum
|
||||
- The [LXST](https://github.com/markqvist/lxst) protocol and framework provides real-time audio and signals transport over Reticulum. It includes primitives and utilities for building voice-based applications and hardware devices, such as the `rnphone` program, that can be used to build hardware telephones.
|
||||
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
|
||||
- The Android, Linux, macOS and Windows app [Sideband](https://github.com/markqvist/Sideband) has a graphical interface and many advanced features, such as file transfers, image and voice messages, real-time voice calls, a distributed telemetry system, mapping capabilities and full plugin extensibility.
|
||||
- [MeshChat](https://github.com/liamcottle/reticulum-meshchat) is a user-friendly LXMF client with a web-based interface, that also supports image and voice messages, as well as file transfers. It also includes a built-in page browser for browsing Nomad Network nodes.
|
||||
- The Android, Linux, macOS and Windows app [Sideband](https://github.com/markqvist/Sideband) has a graphical interface and focuses on ease of use.
|
||||
- [MeshChat](https://github.com/liamcottle/reticulum-meshchat) is a user-friendly LXMF client, that also supports voice calls.
|
||||
|
||||
## Where can Reticulum be used?
|
||||
Over practically any medium that can support at least a half-duplex channel
|
||||
|
@ -297,30 +296,23 @@ You can help support the continued development of open, free and private communi
|
|||
```
|
||||
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
|
||||
```
|
||||
- Bitcoin
|
||||
```
|
||||
bc1p4a6axuvl7n9hpapfj8sv5reqj8kz6uxa67d5en70vzrttj0fmcusgxsfk5
|
||||
```
|
||||
- Ethereum
|
||||
```
|
||||
0xae89F3B94fC4AD6563F0864a55F9a697a90261ff
|
||||
0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73
|
||||
```
|
||||
- Bitcoin
|
||||
```
|
||||
35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH
|
||||
```
|
||||
- Liberapay: https://liberapay.com/Reticulum/
|
||||
|
||||
- Ko-Fi: https://ko-fi.com/markqvist
|
||||
|
||||
Are certain features in the development roadmap are important to you or your
|
||||
organisation? Make them a reality quickly by sponsoring their implementation.
|
||||
|
||||
## Cryptographic Primitives
|
||||
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic
|
||||
primitives, with widely available implementations that can be used both on
|
||||
general-purpose CPUs and on microcontrollers.
|
||||
|
||||
One of the primary considerations for choosing this particular set of primitives is
|
||||
that they can be implemented *safely* with relatively few pitfalls, on practically
|
||||
all current computing platforms.
|
||||
|
||||
The primitives listed here **are authoritative**. Anything claiming to be Reticulum,
|
||||
but not using these exact primitives **is not** Reticulum, and possibly an
|
||||
intentionally compromised or weakened clone. The utilised primitives are:
|
||||
general-purpose CPUs and on microcontrollers. The utilised primitives are:
|
||||
|
||||
- Reticulum Identity Keys are 512-bit Curve25519 keysets
|
||||
- A 256-bit Ed25519 key for signatures
|
||||
|
@ -328,15 +320,15 @@ intentionally compromised or weakened clone. The utilised primitives are:
|
|||
- HKDF for key derivation
|
||||
- Encrypted tokens are based on the [Fernet spec](https://github.com/fernet/spec/)
|
||||
- Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
- AES-128 in CBC mode with PKCS7 padding
|
||||
- HMAC using SHA256 for message authentication
|
||||
- IVs must be generated through `os.urandom()` or better
|
||||
- AES-256 in CBC mode with PKCS7 padding
|
||||
- IVs are generated through os.urandom()
|
||||
- No Fernet version and timestamp metadata fields
|
||||
- SHA-256
|
||||
- SHA-512
|
||||
|
||||
In the default installation configuration, the `X25519`, `Ed25519`,
|
||||
and `AES-256-CBC` primitives are provided by [OpenSSL](https://www.openssl.org/)
|
||||
In the default installation configuration, the `X25519`, `Ed25519` and
|
||||
`AES-128-CBC` primitives are provided by [OpenSSL](https://www.openssl.org/)
|
||||
(via the [PyCA/cryptography](https://github.com/pyca/cryptography) package).
|
||||
The hashing functions `SHA-256` and `SHA-512` are provided by the standard
|
||||
Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `HKDF`,
|
||||
|
@ -350,19 +342,13 @@ provided by the following internal implementations:
|
|||
|
||||
|
||||
Reticulum also includes a complete implementation of all necessary primitives
|
||||
in pure Python. If OpenSSL and PyCA are not available on the system when
|
||||
in pure Python. If OpenSSL & PyCA are not available on the system when
|
||||
Reticulum is started, Reticulum will instead use the internal pure-python
|
||||
primitives. A trivial consequence of this is performance, with the OpenSSL
|
||||
backend being *much* faster. The most important consequence however, is the
|
||||
potential loss of security by using primitives that has not seen the same
|
||||
amount of scrutiny, testing and review as those from OpenSSL.
|
||||
|
||||
Please note that by default, installing Reticulum will **require** OpenSSL and
|
||||
PyCA to also be automatically installed if not already available. It is only
|
||||
possible to use the pure-python primitives if this requirement is specifically
|
||||
overridden by the user, for example by installing the `rnspure` package instead
|
||||
of the normal `rns` package, or by running directly from local source-code.
|
||||
|
||||
If you want to use the internal pure-python primitives, it is **highly
|
||||
advisable** that you have a good understanding of the risks that this pose, and
|
||||
make an informed decision on whether those risks are acceptable to you.
|
||||
|
@ -386,8 +372,7 @@ projects:
|
|||
- [PyCA/cryptography](https://github.com/pyca/cryptography), *BSD License*
|
||||
- [Pure-25519](https://github.com/warner/python-pure25519) by [Brian Warner](https://github.com/warner), *MIT License*
|
||||
- [Pysha2](https://github.com/thomdixon/pysha2) by [Thom Dixon](https://github.com/thomdixon), *MIT License*
|
||||
- [Python AES-128](https://github.com/orgurar/python-aes) by [Or Gur Arie](https://github.com/orgurar), *MIT License*
|
||||
- [Python AES-256](https://github.com/boppreh/aes) by [BoppreH](https://github.com/boppreh), *MIT License*
|
||||
- [Python-AES](https://github.com/orgurar/python-aes) by [Or Gur Arie](https://github.com/orgurar), *MIT License*
|
||||
- [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
|
||||
- [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
|
||||
- [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -32,20 +24,21 @@ import RNS.Cryptography.Provider as cp
|
|||
import RNS.vendor.platformutils as pu
|
||||
|
||||
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
|
||||
from .aes import AES128
|
||||
from .aes import AES256
|
||||
from .aes import AES
|
||||
|
||||
elif cp.PROVIDER == cp.PROVIDER_PYCA:
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
if pu.cryptography_old_api(): from cryptography.hazmat.backends import default_backend
|
||||
|
||||
if pu.cryptography_old_api():
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
class AES_128_CBC:
|
||||
|
||||
@staticmethod
|
||||
def encrypt(plaintext, key, iv):
|
||||
if len(key) != 16: raise ValueError(f"Invalid key length {len(key)*8} for {self}")
|
||||
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
|
||||
cipher = AES128(key)
|
||||
cipher = AES(key)
|
||||
return cipher.encrypt(plaintext, iv)
|
||||
|
||||
elif cp.PROVIDER == cp.PROVIDER_PYCA:
|
||||
|
@ -60,9 +53,8 @@ class AES_128_CBC:
|
|||
|
||||
@staticmethod
|
||||
def decrypt(ciphertext, key, iv):
|
||||
if len(key) != 16: raise ValueError(f"Invalid key length {len(key)*8} for {self}")
|
||||
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
|
||||
cipher = AES128(key)
|
||||
cipher = AES(key)
|
||||
return cipher.decrypt(ciphertext, iv)
|
||||
|
||||
elif cp.PROVIDER == cp.PROVIDER_PYCA:
|
||||
|
@ -74,38 +66,3 @@ class AES_128_CBC:
|
|||
decryptor = cipher.decryptor()
|
||||
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
||||
return plaintext
|
||||
|
||||
class AES_256_CBC:
|
||||
@staticmethod
|
||||
def encrypt(plaintext, key, iv):
|
||||
if len(key) != 32: raise ValueError(f"Invalid key length {len(key)*8} for {self}")
|
||||
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
|
||||
cipher = AES256(key)
|
||||
return cipher.encrypt_cbc(plaintext, iv)
|
||||
|
||||
elif cp.PROVIDER == cp.PROVIDER_PYCA:
|
||||
if not pu.cryptography_old_api():
|
||||
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
|
||||
else:
|
||||
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
|
||||
|
||||
encryptor = cipher.encryptor()
|
||||
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
||||
return ciphertext
|
||||
|
||||
@staticmethod
|
||||
def decrypt(ciphertext, key, iv):
|
||||
if len(key) != 32: raise ValueError(f"Invalid key length {len(key)*8} for {self}")
|
||||
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
|
||||
cipher = AES256(key)
|
||||
return cipher.decrypt_cbc(ciphertext, iv)
|
||||
|
||||
elif cp.PROVIDER == cp.PROVIDER_PYCA:
|
||||
if not pu.cryptography_old_api():
|
||||
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
|
||||
else:
|
||||
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
|
||||
|
||||
decryptor = cipher.decryptor()
|
||||
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
||||
return plaintext
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -33,9 +25,7 @@ import time
|
|||
|
||||
from RNS.Cryptography import HMAC
|
||||
from RNS.Cryptography import PKCS7
|
||||
from RNS.Cryptography import AES
|
||||
from RNS.Cryptography.AES import AES_128_CBC
|
||||
from RNS.Cryptography.AES import AES_256_CBC
|
||||
|
||||
class Token():
|
||||
"""
|
||||
|
@ -50,65 +40,71 @@ class Token():
|
|||
TOKEN_OVERHEAD = 48 # Bytes
|
||||
|
||||
@staticmethod
|
||||
def generate_key(mode=AES_256_CBC):
|
||||
if mode == AES_128_CBC: return os.urandom(32)
|
||||
elif mode == AES_256_CBC: return os.urandom(64)
|
||||
else: raise TypeError(f"Invalid token mode: {mode}")
|
||||
def generate_key():
|
||||
return os.urandom(32)
|
||||
|
||||
def __init__(self, key=None, mode=AES):
|
||||
if key == None: raise ValueError("Token key cannot be None")
|
||||
def __init__(self, key = None):
|
||||
if key == None:
|
||||
raise ValueError("Token key cannot be None")
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("Token key must be 32 bytes, not "+str(len(key)))
|
||||
|
||||
if mode == AES:
|
||||
if len(key) == 32:
|
||||
self.mode = AES_128_CBC
|
||||
self._signing_key = key[:16]
|
||||
self._encryption_key = key[16:]
|
||||
|
||||
elif len(key) == 64:
|
||||
self.mode = AES_256_CBC
|
||||
self._signing_key = key[:32]
|
||||
self._encryption_key = key[32:]
|
||||
|
||||
else: raise ValueError("Token key must be 128 or 256 bits, not "+str(len(key)*8))
|
||||
|
||||
else: raise TypeError(f"Invalid token mode: {mode}")
|
||||
|
||||
|
||||
def verify_hmac(self, token):
|
||||
if len(token) <= 32: raise ValueError("Cannot verify HMAC on token of only "+str(len(token))+" bytes")
|
||||
if len(token) <= 32:
|
||||
raise ValueError("Cannot verify HMAC on token of only "+str(len(token))+" bytes")
|
||||
else:
|
||||
received_hmac = token[-32:]
|
||||
expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
|
||||
|
||||
if received_hmac == expected_hmac: return True
|
||||
else: return False
|
||||
if received_hmac == expected_hmac:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def encrypt(self, data = None):
|
||||
if not isinstance(data, bytes): raise TypeError("Token plaintext input must be bytes")
|
||||
iv = os.urandom(16)
|
||||
current_time = int(time.time())
|
||||
|
||||
ciphertext = self.mode.encrypt(
|
||||
if not isinstance(data, bytes):
|
||||
raise TypeError("Token plaintext input must be bytes")
|
||||
|
||||
ciphertext = AES_128_CBC.encrypt(
|
||||
plaintext = PKCS7.pad(data),
|
||||
key = self._encryption_key,
|
||||
iv = iv)
|
||||
iv = iv,
|
||||
)
|
||||
|
||||
signed_parts = iv+ciphertext
|
||||
|
||||
return signed_parts + HMAC.new(self._signing_key, signed_parts).digest()
|
||||
|
||||
|
||||
def decrypt(self, token = None):
|
||||
if not isinstance(token, bytes): raise TypeError("Token must be bytes")
|
||||
if not self.verify_hmac(token): raise ValueError("Token HMAC was invalid")
|
||||
if not isinstance(token, bytes):
|
||||
raise TypeError("Token must be bytes")
|
||||
|
||||
if not self.verify_hmac(token):
|
||||
raise ValueError("Token HMAC was invalid")
|
||||
|
||||
iv = token[:16]
|
||||
ciphertext = token[16:-32]
|
||||
|
||||
try:
|
||||
return PKCS7.unpad(
|
||||
self.mode.decrypt(
|
||||
ciphertext = ciphertext,
|
||||
key = self._encryption_key,
|
||||
iv = iv))
|
||||
plaintext = PKCS7.unpad(
|
||||
AES_128_CBC.decrypt(
|
||||
ciphertext,
|
||||
self._encryption_key,
|
||||
iv,
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e: raise ValueError(f"Could not decrypt token: {e}")
|
||||
return plaintext
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError("Could not decrypt token")
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2022-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
from .aes128 import AES128
|
||||
from .aes256 import AES256
|
||||
from .aes import AES
|
271
RNS/Cryptography/aes/aes.py
Normal file
271
RNS/Cryptography/aes/aes.py
Normal file
|
@ -0,0 +1,271 @@
|
|||
# MIT License
|
||||
|
||||
# Copyright (c) 2021 Or Gur Arie
|
||||
|
||||
# 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 .utils import *
|
||||
|
||||
|
||||
class AES:
|
||||
# AES-128 block size
|
||||
block_size = 16
|
||||
# AES-128 encrypts messages with 10 rounds
|
||||
_rounds = 10
|
||||
|
||||
|
||||
# initiate the AES objecy
|
||||
def __init__(self, key):
|
||||
"""
|
||||
Initializes the object with a given key.
|
||||
"""
|
||||
# make sure key length is right
|
||||
assert len(key) == AES.block_size
|
||||
|
||||
# ExpandKey
|
||||
self._round_keys = self._expand_key(key)
|
||||
|
||||
|
||||
# will perform the AES ExpandKey phase
|
||||
def _expand_key(self, master_key):
|
||||
"""
|
||||
Expands and returns a list of key matrices for the given master_key.
|
||||
"""
|
||||
|
||||
# Initialize round keys with raw key material.
|
||||
key_columns = bytes2matrix(master_key)
|
||||
iteration_size = len(master_key) // 4
|
||||
|
||||
# Each iteration has exactly as many columns as the key material.
|
||||
i = 1
|
||||
while len(key_columns) < (self._rounds + 1) * 4:
|
||||
# Copy previous word.
|
||||
word = list(key_columns[-1])
|
||||
|
||||
# Perform schedule_core once every "row".
|
||||
if len(key_columns) % iteration_size == 0:
|
||||
# Circular shift.
|
||||
word.append(word.pop(0))
|
||||
# Map to S-BOX.
|
||||
word = [s_box[b] for b in word]
|
||||
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
|
||||
word[0] ^= r_con[i]
|
||||
i += 1
|
||||
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
|
||||
# Run word through S-box in the fourth iteration when using a
|
||||
# 256-bit key.
|
||||
word = [s_box[b] for b in word]
|
||||
|
||||
# XOR with equivalent word from previous iteration.
|
||||
word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
|
||||
key_columns.append(word)
|
||||
|
||||
# Group key words in 4x4 byte matrices.
|
||||
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
|
||||
|
||||
|
||||
# encrypt a single block of data with AES
|
||||
def _encrypt_block(self, plaintext):
|
||||
"""
|
||||
Encrypts a single block of 16 byte long plaintext.
|
||||
"""
|
||||
# length of a single block
|
||||
assert len(plaintext) == AES.block_size
|
||||
|
||||
# perform on a matrix
|
||||
state = bytes2matrix(plaintext)
|
||||
|
||||
# AddRoundKey
|
||||
add_round_key(state, self._round_keys[0])
|
||||
|
||||
# 9 main rounds
|
||||
for i in range(1, self._rounds):
|
||||
# SubBytes
|
||||
sub_bytes(state)
|
||||
# ShiftRows
|
||||
shift_rows(state)
|
||||
# MixCols
|
||||
mix_columns(state)
|
||||
# AddRoundKey
|
||||
add_round_key(state, self._round_keys[i])
|
||||
|
||||
# last round, w/t AddRoundKey step
|
||||
sub_bytes(state)
|
||||
shift_rows(state)
|
||||
add_round_key(state, self._round_keys[-1])
|
||||
|
||||
# return the encrypted matrix as bytes
|
||||
return matrix2bytes(state)
|
||||
|
||||
|
||||
# decrypt a single block of data with AES
|
||||
def _decrypt_block(self, ciphertext):
|
||||
"""
|
||||
Decrypts a single block of 16 byte long ciphertext.
|
||||
"""
|
||||
# length of a single block
|
||||
assert len(ciphertext) == AES.block_size
|
||||
|
||||
# perform on a matrix
|
||||
state = bytes2matrix(ciphertext)
|
||||
|
||||
# in reverse order, last round is first
|
||||
add_round_key(state, self._round_keys[-1])
|
||||
inv_shift_rows(state)
|
||||
inv_sub_bytes(state)
|
||||
|
||||
for i in range(self._rounds - 1, 0, -1):
|
||||
# nain rounds
|
||||
add_round_key(state, self._round_keys[i])
|
||||
inv_mix_columns(state)
|
||||
inv_shift_rows(state)
|
||||
inv_sub_bytes(state)
|
||||
|
||||
# initial AddRoundKey phase
|
||||
add_round_key(state, self._round_keys[0])
|
||||
|
||||
# return bytes
|
||||
return matrix2bytes(state)
|
||||
|
||||
|
||||
# will encrypt the entire data
|
||||
def encrypt(self, plaintext, iv):
|
||||
"""
|
||||
Encrypts `plaintext` using CBC mode and PKCS#7 padding, with the given
|
||||
initialization vector (iv).
|
||||
"""
|
||||
# iv length must be same as block size
|
||||
assert len(iv) == AES.block_size
|
||||
|
||||
assert len(plaintext) % AES.block_size == 0
|
||||
|
||||
ciphertext_blocks = []
|
||||
|
||||
previous = iv
|
||||
for plaintext_block in split_blocks(plaintext):
|
||||
# in CBC mode every block is XOR'd with the previous block
|
||||
xorred = xor_bytes(plaintext_block, previous)
|
||||
|
||||
# encrypt current block
|
||||
block = self._encrypt_block(xorred)
|
||||
previous = block
|
||||
|
||||
# append to ciphertext
|
||||
ciphertext_blocks.append(block)
|
||||
|
||||
# return as bytes
|
||||
return b''.join(ciphertext_blocks)
|
||||
|
||||
|
||||
# will decrypt the entire data
|
||||
def decrypt(self, ciphertext, iv):
|
||||
"""
|
||||
Decrypts `ciphertext` using CBC mode and PKCS#7 padding, with the given
|
||||
initialization vector (iv).
|
||||
"""
|
||||
# iv length must be same as block size
|
||||
assert len(iv) == AES.block_size
|
||||
|
||||
plaintext_blocks = []
|
||||
|
||||
previous = iv
|
||||
for ciphertext_block in split_blocks(ciphertext):
|
||||
# in CBC mode every block is XOR'd with the previous block
|
||||
xorred = xor_bytes(previous, self._decrypt_block(ciphertext_block))
|
||||
|
||||
# append plaintext
|
||||
plaintext_blocks.append(xorred)
|
||||
previous = ciphertext_block
|
||||
|
||||
return b''.join(plaintext_blocks)
|
||||
|
||||
|
||||
def test():
|
||||
# modules and classes requiered for test only
|
||||
import os
|
||||
class bcolors:
|
||||
OK = '\033[92m' #GREEN
|
||||
WARNING = '\033[93m' #YELLOW
|
||||
FAIL = '\033[91m' #RED
|
||||
RESET = '\033[0m' #RESET COLOR
|
||||
|
||||
# will test AES class by performing an encryption / decryption
|
||||
print("AES Tests")
|
||||
print("=========")
|
||||
|
||||
# generate a secret key and print details
|
||||
key = os.urandom(AES.block_size)
|
||||
_aes = AES(key)
|
||||
print(f"Algorithm: AES-CBC-{AES.block_size*8}")
|
||||
print(f"Secret Key: {key.hex()}")
|
||||
print()
|
||||
|
||||
# test single block encryption / decryption
|
||||
iv = os.urandom(AES.block_size)
|
||||
|
||||
single_block_text = b"SingleBlock Text"
|
||||
print("Single Block Tests")
|
||||
print("------------------")
|
||||
print(f"iv: {iv.hex()}")
|
||||
|
||||
print(f"plain text: '{single_block_text.decode()}'")
|
||||
ciphertext_block = _aes._encrypt_block(single_block_text)
|
||||
plaintext_block = _aes._decrypt_block(ciphertext_block)
|
||||
print(f"Ciphertext Hex: {ciphertext_block.hex()}")
|
||||
print(f"Plaintext: {plaintext_block.decode()}")
|
||||
assert plaintext_block == single_block_text
|
||||
print(bcolors.OK + "Single Block Test Passed Successfully" + bcolors.RESET)
|
||||
print()
|
||||
|
||||
# test a less than a block length phrase
|
||||
iv = os.urandom(AES.block_size)
|
||||
|
||||
short_text = b"Just Text"
|
||||
print("Short Text Tests")
|
||||
print("----------------")
|
||||
print(f"iv: {iv.hex()}")
|
||||
print(f"plain text: '{short_text.decode()}'")
|
||||
ciphertext_short = _aes.encrypt(short_text, iv)
|
||||
plaintext_short = _aes.decrypt(ciphertext_short, iv)
|
||||
print(f"Ciphertext Hex: {ciphertext_short.hex()}")
|
||||
print(f"Plaintext: {plaintext_short.decode()}")
|
||||
assert short_text == plaintext_short
|
||||
print(bcolors.OK + "Short Text Test Passed Successfully" + bcolors.RESET)
|
||||
print()
|
||||
|
||||
# test an arbitrary length phrase
|
||||
iv = os.urandom(AES.block_size)
|
||||
|
||||
text = b"This Text is longer than one block"
|
||||
print("Arbitrary Length Tests")
|
||||
print("----------------------")
|
||||
print(f"iv: {iv.hex()}")
|
||||
print(f"plain text: '{text.decode()}'")
|
||||
ciphertext = _aes.encrypt(text, iv)
|
||||
plaintext = _aes.decrypt(ciphertext, iv)
|
||||
print(f"Ciphertext Hex: {ciphertext.hex()}")
|
||||
print(f"Plaintext: {plaintext.decode()}")
|
||||
assert text == plaintext
|
||||
print(bcolors.OK + "Arbitrary Length Text Test Passed Successfully" + bcolors.RESET)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test AES class
|
||||
test()
|
|
@ -1,326 +0,0 @@
|
|||
# MIT License
|
||||
|
||||
# Copyright (c) 2021 Or Gur Arie
|
||||
|
||||
# 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.
|
||||
|
||||
## AES lookup tables
|
||||
# resource: https://en.wikipedia.org/wiki/Rijndael_S-box
|
||||
s_box = (
|
||||
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
||||
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
||||
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
|
||||
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
|
||||
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
|
||||
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
|
||||
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
|
||||
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
|
||||
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
|
||||
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
|
||||
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
|
||||
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
|
||||
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
|
||||
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
|
||||
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
|
||||
)
|
||||
|
||||
inv_s_box = (
|
||||
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
|
||||
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
|
||||
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
|
||||
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
|
||||
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
|
||||
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
|
||||
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
|
||||
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
|
||||
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
|
||||
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
|
||||
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
|
||||
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
|
||||
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
|
||||
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
|
||||
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
|
||||
)
|
||||
|
||||
|
||||
## AES AddRoundKey
|
||||
# Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
|
||||
r_con = (
|
||||
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
|
||||
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
|
||||
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
|
||||
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
|
||||
)
|
||||
|
||||
def add_round_key(s, k):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] ^= k[i][j]
|
||||
|
||||
|
||||
## AES SubBytes
|
||||
def sub_bytes(s):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] = s_box[s[i][j]]
|
||||
|
||||
|
||||
def inv_sub_bytes(s):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] = inv_s_box[s[i][j]]
|
||||
|
||||
|
||||
## AES ShiftRows
|
||||
def shift_rows(s):
|
||||
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
|
||||
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
|
||||
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
|
||||
|
||||
|
||||
def inv_shift_rows(s):
|
||||
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
|
||||
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
|
||||
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
|
||||
|
||||
|
||||
## AES MixColumns
|
||||
# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
|
||||
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
|
||||
|
||||
|
||||
def mix_single_column(a):
|
||||
# see Sec 4.1.2 in The Design of Rijndael
|
||||
t = a[0] ^ a[1] ^ a[2] ^ a[3]
|
||||
u = a[0]
|
||||
a[0] ^= t ^ xtime(a[0] ^ a[1])
|
||||
a[1] ^= t ^ xtime(a[1] ^ a[2])
|
||||
a[2] ^= t ^ xtime(a[2] ^ a[3])
|
||||
a[3] ^= t ^ xtime(a[3] ^ u)
|
||||
|
||||
|
||||
def mix_columns(s):
|
||||
for i in range(4):
|
||||
mix_single_column(s[i])
|
||||
|
||||
|
||||
def inv_mix_columns(s):
|
||||
# see Sec 4.1.3 in The Design of Rijndael
|
||||
for i in range(4):
|
||||
u = xtime(xtime(s[i][0] ^ s[i][2]))
|
||||
v = xtime(xtime(s[i][1] ^ s[i][3]))
|
||||
s[i][0] ^= u
|
||||
s[i][1] ^= v
|
||||
s[i][2] ^= u
|
||||
s[i][3] ^= v
|
||||
|
||||
mix_columns(s)
|
||||
|
||||
## AES Bytes
|
||||
def bytes2matrix(text):
|
||||
""" Converts a 16-byte array into a 4x4 matrix. """
|
||||
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
|
||||
|
||||
def matrix2bytes(matrix):
|
||||
""" Converts a 4x4 matrix into a 16-byte array. """
|
||||
return bytes(sum(matrix, []))
|
||||
|
||||
|
||||
def xor_bytes(a, b):
|
||||
""" Returns a new byte array with the elements xor'ed. """
|
||||
return bytes(i^j for i, j in zip(a, b))
|
||||
|
||||
|
||||
def split_blocks(message, block_size=16, require_padding=True):
|
||||
assert len(message) % block_size == 0 or not require_padding
|
||||
return [message[i:i+16] for i in range(0, len(message), block_size)]
|
||||
|
||||
class AES128:
|
||||
# AES-128 block size
|
||||
block_size = 16
|
||||
# AES-128 encrypts messages with 10 rounds
|
||||
_rounds = 10
|
||||
|
||||
|
||||
# initiate the AES objecy
|
||||
def __init__(self, key):
|
||||
"""
|
||||
Initializes the object with a given key.
|
||||
"""
|
||||
# make sure key length is right
|
||||
assert len(key) == AES128.block_size
|
||||
|
||||
# ExpandKey
|
||||
self._round_keys = self._expand_key(key)
|
||||
|
||||
|
||||
# will perform the AES ExpandKey phase
|
||||
def _expand_key(self, master_key):
|
||||
"""
|
||||
Expands and returns a list of key matrices for the given master_key.
|
||||
"""
|
||||
|
||||
# Initialize round keys with raw key material.
|
||||
key_columns = bytes2matrix(master_key)
|
||||
iteration_size = len(master_key) // 4
|
||||
|
||||
# Each iteration has exactly as many columns as the key material.
|
||||
i = 1
|
||||
while len(key_columns) < (self._rounds + 1) * 4:
|
||||
# Copy previous word.
|
||||
word = list(key_columns[-1])
|
||||
|
||||
# Perform schedule_core once every "row".
|
||||
if len(key_columns) % iteration_size == 0:
|
||||
# Circular shift.
|
||||
word.append(word.pop(0))
|
||||
# Map to S-BOX.
|
||||
word = [s_box[b] for b in word]
|
||||
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
|
||||
word[0] ^= r_con[i]
|
||||
i += 1
|
||||
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
|
||||
# Run word through S-box in the fourth iteration when using a
|
||||
# 256-bit key.
|
||||
word = [s_box[b] for b in word]
|
||||
|
||||
# XOR with equivalent word from previous iteration.
|
||||
word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
|
||||
key_columns.append(word)
|
||||
|
||||
# Group key words in 4x4 byte matrices.
|
||||
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
|
||||
|
||||
|
||||
# encrypt a single block of data with AES
|
||||
def _encrypt_block(self, plaintext):
|
||||
"""
|
||||
Encrypts a single block of 16 byte long plaintext.
|
||||
"""
|
||||
# length of a single block
|
||||
assert len(plaintext) == AES128.block_size
|
||||
|
||||
# perform on a matrix
|
||||
state = bytes2matrix(plaintext)
|
||||
|
||||
# AddRoundKey
|
||||
add_round_key(state, self._round_keys[0])
|
||||
|
||||
# 9 main rounds
|
||||
for i in range(1, self._rounds):
|
||||
# SubBytes
|
||||
sub_bytes(state)
|
||||
# ShiftRows
|
||||
shift_rows(state)
|
||||
# MixCols
|
||||
mix_columns(state)
|
||||
# AddRoundKey
|
||||
add_round_key(state, self._round_keys[i])
|
||||
|
||||
# last round, w/t AddRoundKey step
|
||||
sub_bytes(state)
|
||||
shift_rows(state)
|
||||
add_round_key(state, self._round_keys[-1])
|
||||
|
||||
# return the encrypted matrix as bytes
|
||||
return matrix2bytes(state)
|
||||
|
||||
|
||||
# decrypt a single block of data with AES
|
||||
def _decrypt_block(self, ciphertext):
|
||||
"""
|
||||
Decrypts a single block of 16 byte long ciphertext.
|
||||
"""
|
||||
# length of a single block
|
||||
assert len(ciphertext) == AES128.block_size
|
||||
|
||||
# perform on a matrix
|
||||
state = bytes2matrix(ciphertext)
|
||||
|
||||
# in reverse order, last round is first
|
||||
add_round_key(state, self._round_keys[-1])
|
||||
inv_shift_rows(state)
|
||||
inv_sub_bytes(state)
|
||||
|
||||
for i in range(self._rounds - 1, 0, -1):
|
||||
# nain rounds
|
||||
add_round_key(state, self._round_keys[i])
|
||||
inv_mix_columns(state)
|
||||
inv_shift_rows(state)
|
||||
inv_sub_bytes(state)
|
||||
|
||||
# initial AddRoundKey phase
|
||||
add_round_key(state, self._round_keys[0])
|
||||
|
||||
# return bytes
|
||||
return matrix2bytes(state)
|
||||
|
||||
|
||||
# will encrypt the entire data
|
||||
def encrypt(self, plaintext, iv):
|
||||
"""
|
||||
Encrypts `plaintext` using CBC mode and PKCS#7 padding, with the given
|
||||
initialization vector (iv).
|
||||
"""
|
||||
# iv length must be same as block size
|
||||
assert len(iv) == AES128.block_size
|
||||
|
||||
assert len(plaintext) % AES128.block_size == 0
|
||||
|
||||
ciphertext_blocks = []
|
||||
|
||||
previous = iv
|
||||
for plaintext_block in split_blocks(plaintext):
|
||||
# in CBC mode every block is XOR'd with the previous block
|
||||
xorred = xor_bytes(plaintext_block, previous)
|
||||
|
||||
# encrypt current block
|
||||
block = self._encrypt_block(xorred)
|
||||
previous = block
|
||||
|
||||
# append to ciphertext
|
||||
ciphertext_blocks.append(block)
|
||||
|
||||
# return as bytes
|
||||
return b''.join(ciphertext_blocks)
|
||||
|
||||
|
||||
# will decrypt the entire data
|
||||
def decrypt(self, ciphertext, iv):
|
||||
"""
|
||||
Decrypts `ciphertext` using CBC mode and PKCS#7 padding, with the given
|
||||
initialization vector (iv).
|
||||
"""
|
||||
# iv length must be same as block size
|
||||
assert len(iv) == AES128.block_size
|
||||
|
||||
plaintext_blocks = []
|
||||
|
||||
previous = iv
|
||||
for ciphertext_block in split_blocks(ciphertext):
|
||||
# in CBC mode every block is XOR'd with the previous block
|
||||
xorred = xor_bytes(previous, self._decrypt_block(ciphertext_block))
|
||||
|
||||
# append plaintext
|
||||
plaintext_blocks.append(xorred)
|
||||
previous = ciphertext_block
|
||||
|
||||
return b''.join(plaintext_blocks)
|
|
@ -1,17 +1,17 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2024 BoppreH
|
||||
#
|
||||
|
||||
# Copyright (c) 2021 Or Gur Arie
|
||||
|
||||
# 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
|
||||
|
@ -20,6 +20,12 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
'''
|
||||
Utils class for AES encryption / decryption
|
||||
'''
|
||||
|
||||
## AES lookup tables
|
||||
# resource: https://en.wikipedia.org/wiki/Rijndael_S-box
|
||||
s_box = (
|
||||
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
||||
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
||||
|
@ -58,33 +64,53 @@ inv_s_box = (
|
|||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
|
||||
)
|
||||
|
||||
def sub_bytes(s):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] = s_box[s[i][j]]
|
||||
|
||||
def inv_sub_bytes(s):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] = inv_s_box[s[i][j]]
|
||||
|
||||
def shift_rows(s):
|
||||
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
|
||||
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
|
||||
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
|
||||
|
||||
def inv_shift_rows(s):
|
||||
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
|
||||
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
|
||||
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
|
||||
## AES AddRoundKey
|
||||
# Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
|
||||
r_con = (
|
||||
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
|
||||
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
|
||||
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
|
||||
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
|
||||
)
|
||||
|
||||
def add_round_key(s, k):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] ^= k[i][j]
|
||||
|
||||
|
||||
## AES SubBytes
|
||||
def sub_bytes(s):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] = s_box[s[i][j]]
|
||||
|
||||
|
||||
def inv_sub_bytes(s):
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
s[i][j] = inv_s_box[s[i][j]]
|
||||
|
||||
|
||||
## AES ShiftRows
|
||||
def shift_rows(s):
|
||||
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
|
||||
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
|
||||
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
|
||||
|
||||
|
||||
def inv_shift_rows(s):
|
||||
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
|
||||
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
|
||||
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
|
||||
|
||||
|
||||
## AES MixColumns
|
||||
# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
|
||||
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
|
||||
|
||||
|
||||
def mix_single_column(a):
|
||||
# see Sec 4.1.2 in The Design of Rijndael
|
||||
t = a[0] ^ a[1] ^ a[2] ^ a[3]
|
||||
|
@ -94,10 +120,12 @@ def mix_single_column(a):
|
|||
a[2] ^= t ^ xtime(a[2] ^ a[3])
|
||||
a[3] ^= t ^ xtime(a[3] ^ u)
|
||||
|
||||
|
||||
def mix_columns(s):
|
||||
for i in range(4):
|
||||
mix_single_column(s[i])
|
||||
|
||||
|
||||
def inv_mix_columns(s):
|
||||
# see Sec 4.1.3 in The Design of Rijndael
|
||||
for i in range(4):
|
||||
|
@ -110,127 +138,22 @@ def inv_mix_columns(s):
|
|||
|
||||
mix_columns(s)
|
||||
|
||||
r_con = (
|
||||
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
|
||||
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
|
||||
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
|
||||
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
|
||||
)
|
||||
|
||||
## AES Bytes
|
||||
def bytes2matrix(text):
|
||||
""" Converts a 16-byte array into a 4x4 matrix. """
|
||||
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
|
||||
|
||||
def matrix2bytes(matrix):
|
||||
""" Converts a 4x4 matrix into a 16-byte array. """
|
||||
return bytes(sum(matrix, []))
|
||||
|
||||
|
||||
def bytes2matrix(text): return [list(text[i:i+4]) for i in range(0, len(text), 4)]
|
||||
def matrix2bytes(matrix): return bytes(sum(matrix, []))
|
||||
def xor_bytes(a, b): return bytes(i^j for i, j in zip(a, b))
|
||||
def xor_bytes(a, b):
|
||||
""" Returns a new byte array with the elements xor'ed. """
|
||||
return bytes(i^j for i, j in zip(a, b))
|
||||
|
||||
def inc_bytes(a):
|
||||
out = list(a)
|
||||
for i in reversed(range(len(out))):
|
||||
if out[i] == 0xFF:
|
||||
out[i] = 0
|
||||
else:
|
||||
out[i] += 1
|
||||
break
|
||||
return bytes(out)
|
||||
|
||||
def split_blocks(message, block_size=16, require_padding=True):
|
||||
assert len(message) % block_size == 0 or not require_padding
|
||||
return [message[i:i+16] for i in range(0, len(message), block_size)]
|
||||
|
||||
class AES256:
|
||||
rounds_by_key_size = {32: 14}
|
||||
def __init__(self, master_key):
|
||||
assert len(master_key) in AES256.rounds_by_key_size
|
||||
self.n_rounds = AES256.rounds_by_key_size[len(master_key)]
|
||||
self._key_matrices = self._expand_key(master_key)
|
||||
|
||||
def _expand_key(self, master_key):
|
||||
# Initialize round keys with raw key material.
|
||||
key_columns = bytes2matrix(master_key)
|
||||
iteration_size = len(master_key) // 4
|
||||
|
||||
i = 1
|
||||
while len(key_columns) < (self.n_rounds + 1) * 4:
|
||||
# Copy previous word.
|
||||
word = list(key_columns[-1])
|
||||
|
||||
# Perform schedule_core once every "row".
|
||||
if len(key_columns) % iteration_size == 0:
|
||||
# Circular shift.
|
||||
word.append(word.pop(0))
|
||||
# Map to S-BOX.
|
||||
word = [s_box[b] for b in word]
|
||||
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
|
||||
word[0] ^= r_con[i]
|
||||
i += 1
|
||||
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
|
||||
# Run word through S-box in the fourth iteration when using a
|
||||
# 256-bit key.
|
||||
word = [s_box[b] for b in word]
|
||||
|
||||
# XOR with equivalent word from previous iteration.
|
||||
word = xor_bytes(word, key_columns[-iteration_size])
|
||||
key_columns.append(word)
|
||||
|
||||
# Group key words in 4x4 byte matrices.
|
||||
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
|
||||
|
||||
def encrypt_block(self, plaintext):
|
||||
assert len(plaintext) == 16
|
||||
|
||||
plain_state = bytes2matrix(plaintext)
|
||||
|
||||
add_round_key(plain_state, self._key_matrices[0])
|
||||
|
||||
for i in range(1, self.n_rounds):
|
||||
sub_bytes(plain_state)
|
||||
shift_rows(plain_state)
|
||||
mix_columns(plain_state)
|
||||
add_round_key(plain_state, self._key_matrices[i])
|
||||
|
||||
sub_bytes(plain_state)
|
||||
shift_rows(plain_state)
|
||||
add_round_key(plain_state, self._key_matrices[-1])
|
||||
|
||||
return matrix2bytes(plain_state)
|
||||
|
||||
def decrypt_block(self, ciphertext):
|
||||
assert len(ciphertext) == 16
|
||||
|
||||
cipher_state = bytes2matrix(ciphertext)
|
||||
|
||||
add_round_key(cipher_state, self._key_matrices[-1])
|
||||
inv_shift_rows(cipher_state)
|
||||
inv_sub_bytes(cipher_state)
|
||||
|
||||
for i in range(self.n_rounds - 1, 0, -1):
|
||||
add_round_key(cipher_state, self._key_matrices[i])
|
||||
inv_mix_columns(cipher_state)
|
||||
inv_shift_rows(cipher_state)
|
||||
inv_sub_bytes(cipher_state)
|
||||
|
||||
add_round_key(cipher_state, self._key_matrices[0])
|
||||
|
||||
return matrix2bytes(cipher_state)
|
||||
|
||||
def encrypt_cbc(self, plaintext, iv):
|
||||
if len(iv) != 16: raise ValueError(f"Invalid IV length: {len(iv)}")
|
||||
blocks = []
|
||||
previous = iv
|
||||
for plaintext_block in split_blocks(plaintext):
|
||||
block = self.encrypt_block(xor_bytes(plaintext_block, previous))
|
||||
blocks.append(block)
|
||||
previous = block
|
||||
|
||||
return b''.join(blocks)
|
||||
|
||||
def decrypt_cbc(self, ciphertext, iv):
|
||||
if len(iv) != 16: raise ValueError(f"Invalid IV length: {len(iv)}")
|
||||
blocks = []
|
||||
previous = iv
|
||||
for ciphertext_block in split_blocks(ciphertext):
|
||||
blocks.append(xor_bytes(previous, self.decrypt_block(ciphertext_block)))
|
||||
previous = ciphertext_block
|
||||
|
||||
return b''.join(blocks)
|
||||
|
||||
__all__ = ["AES256"]
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -210,16 +202,12 @@ class Destination:
|
|||
def _persist_ratchets(self):
|
||||
try:
|
||||
with self.ratchet_file_lock:
|
||||
temp_write_path = self.ratchets_path+".tmp"
|
||||
packed_ratchets = umsgpack.packb(self.ratchets)
|
||||
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
|
||||
ratchets_file = open(temp_write_path, "wb")
|
||||
ratchets_file = open(self.ratchets_path, "wb")
|
||||
ratchets_file.write(umsgpack.packb(persisted_data))
|
||||
ratchets_file.close()
|
||||
if os.path.isfile(self.ratchets_path): os.unlink(self.ratchets_path)
|
||||
os.rename(temp_write_path, self.ratchets_path)
|
||||
except Exception as e:
|
||||
RNS.trace_exception(e)
|
||||
self.ratchets = None
|
||||
self.ratchets_path = None
|
||||
raise OSError("Could not write ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
@ -377,7 +365,7 @@ class Destination:
|
|||
else:
|
||||
self.proof_strategy = proof_strategy
|
||||
|
||||
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None, auto_compress = True):
|
||||
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
|
||||
"""
|
||||
Registers a request handler.
|
||||
|
||||
|
@ -385,15 +373,17 @@ class Destination:
|
|||
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
|
||||
:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
|
||||
:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
|
||||
:param auto_compress: If ``True`` or ``False``, determines whether automatic compression of responses should be carried out. If set to an integer value, responses will only be auto-compressed if under this size in bytes. If omitted, the default compression settings will be followed.
|
||||
:raises: ``ValueError`` if any of the supplied arguments are invalid.
|
||||
"""
|
||||
if path == None or path == "": raise ValueError("Invalid path specified")
|
||||
elif not callable(response_generator): raise ValueError("Invalid response generator specified")
|
||||
elif not allow in Destination.request_policies: raise ValueError("Invalid request policy")
|
||||
if path == None or path == "":
|
||||
raise ValueError("Invalid path specified")
|
||||
elif not callable(response_generator):
|
||||
raise ValueError("Invalid response generator specified")
|
||||
elif not allow in Destination.request_policies:
|
||||
raise ValueError("Invalid request policy")
|
||||
else:
|
||||
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
|
||||
request_handler = [path, response_generator, allow, allowed_list, auto_compress]
|
||||
request_handler = [path, response_generator, allow, allowed_list]
|
||||
self.request_handlers[path_hash] = request_handler
|
||||
|
||||
def deregister_request_handler(self, path):
|
||||
|
@ -417,8 +407,7 @@ class Destination:
|
|||
else:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
packet.ratchet_id = self.latest_ratchet_id
|
||||
if plaintext == None: return False
|
||||
else:
|
||||
if plaintext != None:
|
||||
if packet.packet_type == RNS.Packet.DATA:
|
||||
if self.callbacks.packet != None:
|
||||
try:
|
||||
|
@ -426,8 +415,6 @@ class Destination:
|
|||
except Exception as e:
|
||||
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
return True
|
||||
|
||||
def incoming_link_request(self, data, packet):
|
||||
if self.accept_link_requests:
|
||||
link = RNS.Link.validate_request(self, data, packet)
|
||||
|
@ -463,7 +450,6 @@ class Destination:
|
|||
self.ratchets_path = None
|
||||
RNS.trace_exception(e)
|
||||
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
else:
|
||||
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
|
||||
self.ratchets = []
|
||||
|
@ -489,6 +475,7 @@ class Destination:
|
|||
self.latest_ratchet_time = 0
|
||||
self._reload_ratchets(ratchets_path)
|
||||
|
||||
# TODO: Remove at some point
|
||||
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG)
|
||||
return True
|
||||
|
||||
|
@ -642,7 +629,7 @@ class Destination:
|
|||
RNS.log(f"Decryption still failing after ratchet reload. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if decrypted: RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
|
||||
RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
|
||||
|
||||
return decrypted
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -86,9 +78,6 @@ class Identity:
|
|||
for addressable hashes and other purposes. Non-configurable.
|
||||
"""
|
||||
|
||||
DERIVED_KEY_LENGTH = 512//8
|
||||
DERIVED_KEY_LENGTH_LEGACY = 256//8
|
||||
|
||||
# Storage
|
||||
known_destinations = {}
|
||||
known_ratchets = {}
|
||||
|
@ -680,7 +669,7 @@ class Identity:
|
|||
shared_key = ephemeral_key.exchange(target_public_key)
|
||||
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=Identity.DERIVED_KEY_LENGTH,
|
||||
length=32,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
|
@ -694,45 +683,6 @@ class Identity:
|
|||
else:
|
||||
raise KeyError("Encryption failed because identity does not hold a public key")
|
||||
|
||||
# Post 0.9.6 decryption will only accept AES-256
|
||||
def __decrypt(self, shared_key, ciphertext):
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=Identity.DERIVED_KEY_LENGTH,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context())
|
||||
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
return plaintext
|
||||
|
||||
# This handles decryption during migration to AES-256 where
|
||||
# older instances may still use AES-128. If decryption fails
|
||||
# initially, AES-128 will be attempted as a fallback mode.
|
||||
# This handler will be removed in RNS 0.9.6.
|
||||
def __migration_decrypt(self, shared_key, ciphertext):
|
||||
try:
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=Identity.DERIVED_KEY_LENGTH,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context())
|
||||
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
|
||||
except Exception as e:
|
||||
# RNS.log("Decryption failed, attempting legacy mode fallback", RNS.LOG_DEBUG)
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=Identity.DERIVED_KEY_LENGTH_LEGACY,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context())
|
||||
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
|
||||
return plaintext
|
||||
|
||||
def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False, ratchet_id_receiver=None):
|
||||
"""
|
||||
|
@ -742,7 +692,6 @@ class Identity:
|
|||
:returns: Plaintext as *bytes*, or *None* if decryption fails.
|
||||
:raises: *KeyError* if the instance does not hold a private key.
|
||||
"""
|
||||
|
||||
if self.prv != None:
|
||||
if len(ciphertext_token) > Identity.KEYSIZE//8//2:
|
||||
plaintext = None
|
||||
|
@ -757,7 +706,15 @@ class Identity:
|
|||
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet)
|
||||
ratchet_id = Identity._get_ratchet_id(ratchet_prv.public_key().public_bytes())
|
||||
shared_key = ratchet_prv.exchange(peer_pub)
|
||||
plaintext = self.__migration_decrypt(shared_key, ciphertext)
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = ratchet_id
|
||||
|
||||
|
@ -774,8 +731,15 @@ class Identity:
|
|||
|
||||
if plaintext == None:
|
||||
shared_key = self.prv.exchange(peer_pub)
|
||||
plaintext = self.__migration_decrypt(shared_key, ciphertext)
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = None
|
||||
|
||||
|
@ -784,8 +748,7 @@ class Identity:
|
|||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = None
|
||||
|
||||
return plaintext
|
||||
|
||||
return plaintext;
|
||||
else:
|
||||
RNS.log("Decryption failed because the token size was invalid.", RNS.LOG_DEBUG)
|
||||
return None
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -319,8 +311,7 @@ class BackboneInterface(Interface):
|
|||
client_socket, address = server_socket.accept()
|
||||
client_socket.setblocking(0)
|
||||
if not owner_interface.incoming_connection(client_socket):
|
||||
try: client_socket.close()
|
||||
except Exception as e: RNS.log(f"Error while closing socket for failed incoming connection: {e}", RNS.LOG_ERROR)
|
||||
client_socket.close()
|
||||
|
||||
elif fileno == server_socket.fileno() and (event & select.EPOLLHUP):
|
||||
try: BackboneInterface.deregister_fileno(fileno)
|
||||
|
@ -338,7 +329,6 @@ class BackboneInterface(Interface):
|
|||
|
||||
def incoming_connection(self, socket):
|
||||
RNS.log("Accepting incoming connection", RNS.LOG_VERBOSE)
|
||||
try:
|
||||
spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None}
|
||||
spawned_interface = BackboneClientInterface(self.owner, spawned_configuration, connected_socket=socket)
|
||||
spawned_interface.OUT = self.OUT
|
||||
|
@ -382,10 +372,6 @@ class BackboneInterface(Interface):
|
|||
self.spawned_interfaces.append(spawned_interface)
|
||||
BackboneInterface.add_client_socket(socket, spawned_interface)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while accepting incoming connection on {self}: {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -135,23 +127,23 @@ class Interface:
|
|||
|
||||
def optimise_mtu(self):
|
||||
if self.AUTOCONFIGURE_MTU:
|
||||
if self.bitrate >= 1_000_000_000:
|
||||
if self.bitrate > 500_000_000:
|
||||
self.HW_MTU = 524288
|
||||
elif self.bitrate > 750_000_000:
|
||||
elif self.bitrate > 16_000_000:
|
||||
self.HW_MTU = 262144
|
||||
elif self.bitrate > 400_000_000:
|
||||
elif self.bitrate > 8_000_000:
|
||||
self.HW_MTU = 131072
|
||||
elif self.bitrate > 200_000_000:
|
||||
elif self.bitrate > 4_000_000:
|
||||
self.HW_MTU = 65536
|
||||
elif self.bitrate > 100_000_000:
|
||||
self.HW_MTU = 32768
|
||||
elif self.bitrate > 10_000_000:
|
||||
self.HW_MTU = 16384
|
||||
elif self.bitrate > 5_000_000:
|
||||
self.HW_MTU = 8192
|
||||
elif self.bitrate > 2_000_000:
|
||||
self.HW_MTU = 4096
|
||||
self.HW_MTU = 32768
|
||||
elif self.bitrate > 1_000_000:
|
||||
self.HW_MTU = 16384
|
||||
elif self.bitrate > 500_000:
|
||||
self.HW_MTU = 8192
|
||||
elif self.bitrate > 250_000:
|
||||
self.HW_MTU = 4096
|
||||
elif self.bitrate > 125_000:
|
||||
self.HW_MTU = 2048
|
||||
elif self.bitrate > 62_500:
|
||||
self.HW_MTU = 1024
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -70,7 +62,7 @@ class LocalClientInterface(Interface):
|
|||
self.HW_MTU = 262144
|
||||
self.online = False
|
||||
|
||||
if socket_path != None and RNS.Reticulum.get_instance().use_af_unix: self.socket_path = f"\0rns/{socket_path}"
|
||||
if socket_path != None and RNS.vendor.platformutils.use_af_unix(): self.socket_path = f"\0rns/{socket_path}"
|
||||
else: self.socket_path = None
|
||||
|
||||
self.IN = True
|
||||
|
@ -337,8 +329,8 @@ class LocalClientInterface(Interface):
|
|||
|
||||
|
||||
def __str__(self):
|
||||
if self.socket_path: return "LocalInterface["+str(self.socket_path.replace("\0", ""))+"]"
|
||||
else: return "LocalInterface["+str(self.target_port)+"]"
|
||||
if self.socket_path: return "Shared Instance["+str(self.socket_path.replace("\0", ""))+"]"
|
||||
else: return "Shared Instance["+str(self.target_port)+"]"
|
||||
|
||||
|
||||
class LocalServerInterface(Interface):
|
||||
|
@ -350,7 +342,7 @@ class LocalServerInterface(Interface):
|
|||
self.online = False
|
||||
self.clients = 0
|
||||
|
||||
if socket_path != None and RNS.Reticulum.get_instance().use_af_unix: self.socket_path = f"\0rns/{socket_path}"
|
||||
if socket_path != None and RNS.vendor.platformutils.use_af_unix(): self.socket_path = f"\0rns/{socket_path}"
|
||||
else: self.socket_path = None
|
||||
|
||||
self.IN = True
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2024 Jacob Eva. Adapted from the RNodeInterface by Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
# Copyright (c) 2025 Mark Qvist
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
224
RNS/Link.py
224
RNS/Link.py
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -40,7 +32,6 @@ import struct
|
|||
import math
|
||||
import time
|
||||
import RNS
|
||||
import io
|
||||
|
||||
class LinkCallbacks:
|
||||
def __init__(self):
|
||||
|
@ -80,23 +71,19 @@ class Link:
|
|||
LINK_MTU_SIZE = 3
|
||||
TRAFFIC_TIMEOUT_MIN_MS = 5
|
||||
TRAFFIC_TIMEOUT_FACTOR = 6
|
||||
KEEPALIVE_MAX_RTT = 1.75
|
||||
KEEPALIVE_TIMEOUT_FACTOR = 4
|
||||
"""
|
||||
RTT timeout factor used in link timeout calculation.
|
||||
"""
|
||||
STALE_GRACE = 5
|
||||
STALE_GRACE = 2
|
||||
"""
|
||||
Grace period in seconds used in link timeout calculation.
|
||||
"""
|
||||
KEEPALIVE_MAX = 360
|
||||
KEEPALIVE_MIN = 5
|
||||
KEEPALIVE = KEEPALIVE_MAX
|
||||
KEEPALIVE = 360
|
||||
"""
|
||||
Default interval for sending keep-alive packets on established links in seconds.
|
||||
Interval for sending keep-alive packets on established links in seconds.
|
||||
"""
|
||||
STALE_FACTOR = 2
|
||||
STALE_TIME = STALE_FACTOR*KEEPALIVE
|
||||
STALE_TIME = 2*KEEPALIVE
|
||||
"""
|
||||
If no traffic or keep-alive packets are received within this period, the
|
||||
link will be marked as stale, and a final keep-alive packet will be sent.
|
||||
|
@ -105,8 +92,6 @@ class Link:
|
|||
and will be torn down.
|
||||
"""
|
||||
|
||||
WATCHDOG_MAX_SLEEP = 5
|
||||
|
||||
PENDING = 0x00
|
||||
HANDSHAKE = 0x01
|
||||
ACTIVE = 0x02
|
||||
|
@ -122,65 +107,24 @@ class Link:
|
|||
ACCEPT_ALL = 0x02
|
||||
resource_strategies = [ACCEPT_NONE, ACCEPT_APP, ACCEPT_ALL]
|
||||
|
||||
MODE_AES128_CBC = 0x00
|
||||
MODE_AES256_CBC = 0x01
|
||||
MODE_AES256_GCM = 0x02
|
||||
MODE_OTP_RESERVED = 0x03
|
||||
MODE_PQ_RESERVED_1 = 0x04
|
||||
MODE_PQ_RESERVED_2 = 0x05
|
||||
MODE_PQ_RESERVED_3 = 0x06
|
||||
MODE_PQ_RESERVED_4 = 0x07
|
||||
ENABLED_MODES = [MODE_AES128_CBC, MODE_AES256_CBC]
|
||||
MODE_DEFAULT = MODE_AES256_CBC
|
||||
MODE_DESCRIPTIONS = {MODE_AES128_CBC: "AES_128_CBC",
|
||||
MODE_AES256_CBC: "AES_256_CBC",
|
||||
MODE_AES256_GCM: "MODE_AES256_GCM",
|
||||
MODE_OTP_RESERVED: "MODE_OTP_RESERVED",
|
||||
MODE_PQ_RESERVED_1: "MODE_PQ_RESERVED_1",
|
||||
MODE_PQ_RESERVED_2: "MODE_PQ_RESERVED_2",
|
||||
MODE_PQ_RESERVED_3: "MODE_PQ_RESERVED_3",
|
||||
MODE_PQ_RESERVED_4: "MODE_PQ_RESERVED_4"}
|
||||
|
||||
MTU_BYTEMASK = 0x1FFFFF
|
||||
MODE_BYTEMASK = 0xE0
|
||||
|
||||
@staticmethod
|
||||
def signalling_bytes(mtu, mode):
|
||||
if not mode in Link.ENABLED_MODES: raise TypeError(f"Requested link mode {Link.MODE_DESCRIPTIONS[mode]} not enabled")
|
||||
signalling_value = (mtu & Link.MTU_BYTEMASK)+(((mode<<5) & Link.MODE_BYTEMASK)<<16)
|
||||
return struct.pack(">I", signalling_value)[1:]
|
||||
def mtu_bytes(mtu):
|
||||
return struct.pack(">I", mtu & 0xFFFFFF)[1:]
|
||||
|
||||
@staticmethod
|
||||
def mtu_from_lr_packet(packet):
|
||||
if len(packet.data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
|
||||
return (packet.data[Link.ECPUBSIZE] << 16) + (packet.data[Link.ECPUBSIZE+1] << 8) + (packet.data[Link.ECPUBSIZE+2]) & Link.MTU_BYTEMASK
|
||||
else: return None
|
||||
return (packet.data[Link.ECPUBSIZE] << 16) + (packet.data[Link.ECPUBSIZE+1] << 8) + (packet.data[Link.ECPUBSIZE+2])
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def mtu_from_lp_packet(packet):
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
|
||||
mtu_bytes = packet.data[RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE]
|
||||
return (mtu_bytes[0] << 16) + (mtu_bytes[1] << 8) + (mtu_bytes[2]) & Link.MTU_BYTEMASK
|
||||
else: return None
|
||||
|
||||
@staticmethod
|
||||
def mode_byte(mode):
|
||||
if mode in Link.ENABLED_MODES: return (mode << 5) & Link.MODE_BYTEMASK
|
||||
else: raise TypeError(f"Requested link mode {mode} not enabled")
|
||||
|
||||
@staticmethod
|
||||
def mode_from_lr_packet(packet):
|
||||
if len(packet.data) > Link.ECPUBSIZE:
|
||||
mode = (packet.data[Link.ECPUBSIZE] & Link.MODE_BYTEMASK) >> 5
|
||||
return mode
|
||||
else: return Link.MODE_DEFAULT
|
||||
|
||||
@staticmethod
|
||||
def mode_from_lp_packet(packet):
|
||||
if len(packet.data) > RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2:
|
||||
mode = packet.data[RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2] >> 5
|
||||
return mode
|
||||
else: return Link.MODE_DEFAULT
|
||||
return (mtu_bytes[0] << 16) + (mtu_bytes[1] << 8) + (mtu_bytes[2])
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def validate_request(owner, data, packet):
|
||||
|
@ -197,11 +141,6 @@ class Link:
|
|||
RNS.trace_exception(e)
|
||||
link.mtu = RNS.Reticulum.MTU
|
||||
|
||||
link.mode = Link.mode_from_lr_packet(packet)
|
||||
|
||||
# TODO: Remove debug
|
||||
RNS.log(f"Incoming link request with mode {Link.MODE_DESCRIPTIONS[link.mode]}", RNS.LOG_DEBUG)
|
||||
|
||||
link.update_mdu()
|
||||
link.destination = packet.destination
|
||||
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops) + Link.KEEPALIVE
|
||||
|
@ -230,9 +169,9 @@ class Link:
|
|||
return None
|
||||
|
||||
|
||||
def __init__(self, destination=None, established_callback=None, closed_callback=None, owner=None, peer_pub_bytes=None, peer_sig_pub_bytes=None, mode=MODE_DEFAULT):
|
||||
if destination != None and destination.type != RNS.Destination.SINGLE: raise TypeError("Links can only be established to the \"single\" destination type")
|
||||
self.mode = mode
|
||||
def __init__(self, destination=None, established_callback = None, closed_callback = None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
|
||||
if destination != None and destination.type != RNS.Destination.SINGLE:
|
||||
raise TypeError("Links can only be established to the \"single\" destination type")
|
||||
self.rtt = None
|
||||
self.mtu = RNS.Reticulum.MTU
|
||||
self.establishment_cost = 0
|
||||
|
@ -247,7 +186,6 @@ class Link:
|
|||
self.pending_requests = []
|
||||
self.last_inbound = 0
|
||||
self.last_outbound = 0
|
||||
self.last_keepalive = 0
|
||||
self.last_proof = 0
|
||||
self.last_data = 0
|
||||
self.tx = 0
|
||||
|
@ -306,14 +244,12 @@ class Link:
|
|||
self.set_link_closed_callback(closed_callback)
|
||||
|
||||
if self.initiator:
|
||||
signalling_bytes = b""
|
||||
link_mtu = b""
|
||||
nh_hw_mtu = RNS.Transport.next_hop_interface_hw_mtu(destination.hash)
|
||||
if RNS.Reticulum.link_mtu_discovery() and nh_hw_mtu:
|
||||
signalling_bytes = Link.signalling_bytes(nh_hw_mtu, self.mode)
|
||||
link_mtu = Link.mtu_bytes(nh_hw_mtu)
|
||||
RNS.log(f"Signalling link MTU of {RNS.prettysize(nh_hw_mtu)} for link", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
else: signalling_bytes = Link.signalling_bytes(RNS.Reticulum.MTU, self.mode)
|
||||
RNS.log(f"Establishing link with mode {Link.MODE_DESCRIPTIONS[self.mode]}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
self.request_data = self.pub_bytes+self.sig_pub_bytes+signalling_bytes
|
||||
self.request_data = self.pub_bytes+self.sig_pub_bytes+link_mtu
|
||||
self.packet = RNS.Packet(destination, self.request_data, packet_type=RNS.Packet.LINKREQUEST)
|
||||
self.packet.pack()
|
||||
self.establishment_cost += len(self.packet.raw)
|
||||
|
@ -355,25 +291,25 @@ class Link:
|
|||
self.status = Link.HANDSHAKE
|
||||
self.shared_key = self.prv.exchange(self.peer_pub)
|
||||
|
||||
if self.mode == Link.MODE_AES128_CBC: derived_key_length = 32
|
||||
elif self.mode == Link.MODE_AES256_CBC: derived_key_length = 64
|
||||
else: raise TypeError(f"Invalid link mode {self.mode} on {self}")
|
||||
|
||||
self.derived_key = RNS.Cryptography.hkdf(
|
||||
length=derived_key_length,
|
||||
length=32,
|
||||
derive_from=self.shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context())
|
||||
|
||||
else: RNS.log("Handshake attempt on "+str(self)+" with invalid state "+str(self.status), RNS.LOG_ERROR)
|
||||
context=self.get_context(),
|
||||
)
|
||||
else:
|
||||
RNS.log("Handshake attempt on "+str(self)+" with invalid state "+str(self.status), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def prove(self):
|
||||
signalling_bytes = Link.signalling_bytes(self.mtu, self.mode)
|
||||
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes+signalling_bytes
|
||||
mtu_bytes = b""
|
||||
if self.mtu != RNS.Reticulum.MTU:
|
||||
mtu_bytes = Link.mtu_bytes(self.mtu)
|
||||
|
||||
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes+mtu_bytes
|
||||
signature = self.owner.identity.sign(signed_data)
|
||||
|
||||
proof_data = signature+self.pub_bytes+signalling_bytes
|
||||
proof_data = signature+self.pub_bytes+mtu_bytes
|
||||
proof = RNS.Packet(self, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.LRPROOF)
|
||||
proof.send()
|
||||
self.establishment_cost += len(proof.raw)
|
||||
|
@ -396,14 +332,11 @@ class Link:
|
|||
def validate_proof(self, packet):
|
||||
try:
|
||||
if self.status == Link.PENDING:
|
||||
signalling_bytes = b""
|
||||
mtu_bytes = b""
|
||||
confirmed_mtu = None
|
||||
mode = Link.mode_from_lp_packet(packet)
|
||||
RNS.log(f"Validating link request proof with mode {Link.MODE_DESCRIPTIONS[mode]}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
if mode != self.mode: raise TypeError(f"Invalid link mode {mode} in link request proof")
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
|
||||
confirmed_mtu = Link.mtu_from_lp_packet(packet)
|
||||
signalling_bytes = Link.signalling_bytes(confirmed_mtu, mode)
|
||||
mtu_bytes = Link.mtu_bytes(confirmed_mtu)
|
||||
packet.data = packet.data[:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2]
|
||||
RNS.log(f"Destination confirmed link MTU of {RNS.prettysize(confirmed_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
|
||||
|
@ -414,7 +347,7 @@ class Link:
|
|||
self.handshake()
|
||||
|
||||
self.establishment_cost += len(packet.raw)
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes+signalling_bytes
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes+mtu_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if self.destination.identity.validate(signature, signed_data):
|
||||
|
@ -430,13 +363,11 @@ class Link:
|
|||
self.activated_at = time.time()
|
||||
self.last_proof = self.activated_at
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+RNS.prettyshorttime(self.rtt), RNS.LOG_DEBUG)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_DEBUG)
|
||||
|
||||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||
self.establishment_rate = self.establishment_cost/self.rtt
|
||||
|
||||
self.__update_keepalive()
|
||||
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
rtt_packet.send()
|
||||
|
@ -544,8 +475,6 @@ class Link:
|
|||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||
self.establishment_rate = self.establishment_cost/self.rtt
|
||||
|
||||
self.__update_keepalive()
|
||||
|
||||
try:
|
||||
if self.owner.callbacks.link_established != None:
|
||||
self.owner.callbacks.link_established(self)
|
||||
|
@ -633,12 +562,6 @@ class Link:
|
|||
else:
|
||||
return None
|
||||
|
||||
def get_mode(self):
|
||||
"""
|
||||
:returns: The mode of an established link.
|
||||
"""
|
||||
return self.mode
|
||||
|
||||
def get_salt(self):
|
||||
return self.link_id
|
||||
|
||||
|
@ -688,23 +611,23 @@ class Link:
|
|||
|
||||
def had_outbound(self, is_keepalive=False):
|
||||
self.last_outbound = time.time()
|
||||
if not is_keepalive: self.last_data = self.last_outbound
|
||||
else: self.last_keepalive = self.last_outbound
|
||||
|
||||
def __teardown_packet(self):
|
||||
teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE)
|
||||
teardown_packet.send()
|
||||
self.had_outbound()
|
||||
if not is_keepalive:
|
||||
self.last_data = self.last_outbound
|
||||
|
||||
def teardown(self):
|
||||
"""
|
||||
Closes the link and purges encryption keys. New keys will
|
||||
be used if a new link to the same destination is established.
|
||||
"""
|
||||
if self.status != Link.PENDING and self.status != Link.CLOSED: self.__teardown_packet()
|
||||
if self.status != Link.PENDING and self.status != Link.CLOSED:
|
||||
teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE)
|
||||
teardown_packet.send()
|
||||
self.had_outbound()
|
||||
self.status = Link.CLOSED
|
||||
if self.initiator: self.teardown_reason = Link.INITIATOR_CLOSED
|
||||
else: self.teardown_reason = Link.DESTINATION_CLOSED
|
||||
if self.initiator:
|
||||
self.teardown_reason = Link.INITIATOR_CLOSED
|
||||
else:
|
||||
self.teardown_reason = Link.DESTINATION_CLOSED
|
||||
self.link_closed()
|
||||
|
||||
def teardown_packet(self, packet):
|
||||
|
@ -791,10 +714,9 @@ class Link:
|
|||
elif self.status == Link.ACTIVE:
|
||||
activated_at = self.activated_at if self.activated_at != None else 0
|
||||
last_inbound = max(max(self.last_inbound, self.last_proof), activated_at)
|
||||
now = time.time()
|
||||
|
||||
if now >= last_inbound + self.keepalive:
|
||||
if self.initiator and now >= self.last_keepalive + self.keepalive:
|
||||
if time.time() >= last_inbound + self.keepalive:
|
||||
if self.initiator:
|
||||
self.send_keepalive()
|
||||
|
||||
if time.time() >= last_inbound + self.stale_time:
|
||||
|
@ -808,7 +730,6 @@ class Link:
|
|||
|
||||
elif self.status == Link.STALE:
|
||||
sleep_time = 0.001
|
||||
self.__teardown_packet()
|
||||
self.status = Link.CLOSED
|
||||
self.teardown_reason = Link.TIMEOUT
|
||||
self.link_closed()
|
||||
|
@ -821,7 +742,6 @@ class Link:
|
|||
self.teardown()
|
||||
sleep_time = 0.1
|
||||
|
||||
sleep_time = min(sleep_time, Link.WATCHDOG_MAX_SLEEP)
|
||||
sleep(sleep_time)
|
||||
|
||||
if not self.__track_phy_stats:
|
||||
|
@ -845,10 +765,6 @@ class Link:
|
|||
if packet.q != None:
|
||||
self.q = packet.q
|
||||
|
||||
def __update_keepalive(self):
|
||||
self.keepalive = max(min(self.rtt*(Link.KEEPALIVE_MAX/Link.KEEPALIVE_MAX_RTT), Link.KEEPALIVE_MAX), Link.KEEPALIVE_MIN)
|
||||
self.stale_time = self.keepalive * Link.STALE_FACTOR
|
||||
|
||||
def send_keepalive(self):
|
||||
keepalive_packet = RNS.Packet(self, bytes([0xFF]), context=RNS.Packet.KEEPALIVE)
|
||||
keepalive_packet.send()
|
||||
|
@ -866,7 +782,6 @@ class Link:
|
|||
response_generator = request_handler[1]
|
||||
allow = request_handler[2]
|
||||
allowed_list = request_handler[3]
|
||||
auto_compress = request_handler[4]
|
||||
|
||||
allowed = False
|
||||
if not allow == RNS.Destination.ALLOW_NONE:
|
||||
|
@ -885,29 +800,18 @@ class Link:
|
|||
else:
|
||||
raise TypeError("Invalid signature for response generator callback")
|
||||
|
||||
file_response = False
|
||||
file_handle = None
|
||||
if type(response) == list or type(response) == tuple:
|
||||
metadata = None
|
||||
if len(response) > 0 and type(response[0]) == io.BufferedReader:
|
||||
if len(response) > 1: metadata = response[1]
|
||||
file_handle = response[0]
|
||||
file_response = True
|
||||
|
||||
if response != None:
|
||||
if file_response:
|
||||
response_resource = RNS.Resource(file_handle, self, metadata=metadata, request_id = request_id, is_response = True, auto_compress=auto_compress)
|
||||
else:
|
||||
packed_response = umsgpack.packb([request_id, response])
|
||||
|
||||
if len(packed_response) <= self.mdu:
|
||||
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
|
||||
else:
|
||||
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True, auto_compress=auto_compress)
|
||||
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
|
||||
else:
|
||||
identity_string = str(self.get_remote_identity()) if self.get_remote_identity() != None else "<Unknown>"
|
||||
RNS.log("Request "+RNS.prettyhexrep(request_id)+" from "+identity_string+" not allowed for: "+str(path), RNS.LOG_DEBUG)
|
||||
|
||||
def handle_response(self, request_id, response_data, response_size, response_transfer_size, metadata=None):
|
||||
def handle_response(self, request_id, response_data, response_size, response_transfer_size):
|
||||
if self.status == Link.ACTIVE:
|
||||
remove = None
|
||||
for pending_request in self.pending_requests:
|
||||
|
@ -918,7 +822,7 @@ class Link:
|
|||
if pending_request.response_transfer_size == None:
|
||||
pending_request.response_transfer_size = 0
|
||||
pending_request.response_transfer_size += response_transfer_size
|
||||
pending_request.response_received(response_data, metadata)
|
||||
pending_request.response_received(response_data)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
@ -941,21 +845,12 @@ class Link:
|
|||
|
||||
def response_resource_concluded(self, resource):
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
# If the response resource has metadata, this
|
||||
# is a file response, and we'll pass the open
|
||||
# file handle directly.
|
||||
if resource.has_metadata:
|
||||
self.handle_response(resource.request_id, resource.data, resource.total_size, resource.size, metadata=resource.metadata)
|
||||
|
||||
# If not, we'll unpack the response data and
|
||||
# pass the unpacked structure to the handler
|
||||
else:
|
||||
packed_response = resource.data.read()
|
||||
unpacked_response = umsgpack.unpackb(packed_response)
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
self.handle_response(request_id, response_data, resource.total_size, resource.size)
|
||||
|
||||
self.handle_response(request_id, response_data, resource.total_size, resource.size)
|
||||
else:
|
||||
RNS.log("Incoming response resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
|
||||
for pending_request in self.pending_requests:
|
||||
|
@ -1190,7 +1085,8 @@ class Link:
|
|||
def encrypt(self, plaintext):
|
||||
try:
|
||||
if not self.token:
|
||||
try: self.token = Token(self.derived_key)
|
||||
try:
|
||||
self.token = Token(self.derived_key)
|
||||
except Exception as e:
|
||||
RNS.log("Could not instantiate token while performing encryption on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
@ -1204,7 +1100,9 @@ class Link:
|
|||
|
||||
def decrypt(self, ciphertext):
|
||||
try:
|
||||
if not self.token: self.token = Token(self.derived_key)
|
||||
if not self.token:
|
||||
self.token = Token(self.derived_key)
|
||||
|
||||
return self.token.decrypt(ciphertext)
|
||||
|
||||
except Exception as e:
|
||||
|
@ -1379,7 +1277,6 @@ class RequestReceipt():
|
|||
self.response = None
|
||||
self.response_transfer_size = None
|
||||
self.response_size = None
|
||||
self.metadata = None
|
||||
self.status = RequestReceipt.SENT
|
||||
self.sent_at = time.time()
|
||||
self.progress = 0
|
||||
|
@ -1466,11 +1363,10 @@ class RequestReceipt():
|
|||
resource.cancel()
|
||||
|
||||
|
||||
def response_received(self, response, metadata=None):
|
||||
def response_received(self, response):
|
||||
if not self.status == RequestReceipt.FAILED:
|
||||
self.progress = 1.0
|
||||
self.response = response
|
||||
self.metadata = metadata
|
||||
self.status = RequestReceipt.READY
|
||||
self.response_concluded_at = time.time()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -43,10 +35,10 @@ class Packet:
|
|||
|
||||
For ``RNS.Destination.GROUP`` destinations, Reticulum will use the
|
||||
pre-shared key configured for the destination. All packets to group
|
||||
destinations are encrypted with the same AES-256 key.
|
||||
destinations are encrypted with the same AES-128 key.
|
||||
|
||||
For ``RNS.Destination.SINGLE`` destinations, Reticulum will use a newly
|
||||
derived ephemeral AES-256 key for every packet.
|
||||
derived ephemeral AES-128 key for every packet.
|
||||
|
||||
For :ref:`RNS.Link<api-link>` destinations, Reticulum will use per-link
|
||||
ephemeral keys, and offers **Forward Secrecy**.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
215
RNS/Resource.py
215
RNS/Resource.py
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -33,7 +25,6 @@ import os
|
|||
import bz2
|
||||
import math
|
||||
import time
|
||||
import struct
|
||||
import tempfile
|
||||
import threading
|
||||
from threading import Lock
|
||||
|
@ -108,20 +99,22 @@ class Resource:
|
|||
# it is to be handled within reasonable
|
||||
# time constraint, even on small systems.
|
||||
#
|
||||
# A small system in this regard is
|
||||
# defined as a Raspberry Pi, which should
|
||||
# be able to compress, encrypt and hash-map
|
||||
# the resource in about 10 seconds.
|
||||
#
|
||||
# This constant will be used when determining
|
||||
# how to sequence the sending of large resources.
|
||||
#
|
||||
# Capped at 16777215 (0xFFFFFF) per segment to
|
||||
# fit in 3 bytes in resource advertisements.
|
||||
MAX_EFFICIENT_SIZE = 1 * 1024 * 1024 - 1
|
||||
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1
|
||||
RESPONSE_MAX_GRACE_TIME = 10
|
||||
|
||||
# Max metadata size is 16777215 (0xFFFFFF) bytes
|
||||
METADATA_MAX_SIZE = 16 * 1024 * 1024 - 1
|
||||
|
||||
# The maximum size to auto-compress with
|
||||
# bz2 before sending.
|
||||
AUTO_COMPRESS_MAX_SIZE = 64 * 1024 * 1024
|
||||
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
|
||||
|
||||
PART_TIMEOUT_FACTOR = 4
|
||||
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
|
||||
|
@ -195,15 +188,12 @@ class Resource:
|
|||
resource.started_transferring = resource.last_activity
|
||||
|
||||
resource.storagepath = RNS.Reticulum.resourcepath+"/"+resource.original_hash.hex()
|
||||
resource.meta_storagepath = resource.storagepath+".meta"
|
||||
resource.segment_index = adv.i
|
||||
resource.total_segments = adv.l
|
||||
|
||||
if adv.l > 1: resource.split = True
|
||||
else: resource.split = False
|
||||
|
||||
if adv.x: resource.has_metadata = True
|
||||
else: resource.has_metadata = False
|
||||
if adv.l > 1:
|
||||
resource.split = True
|
||||
else:
|
||||
resource.split = False
|
||||
|
||||
resource.hashmap = [None] * resource.total_parts
|
||||
resource.hashmap_height = 0
|
||||
|
@ -229,7 +219,9 @@ class Resource:
|
|||
RNS.log("Error while executing resource started callback from "+str(resource)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
resource.hashmap_update(0, resource.hashmap_raw)
|
||||
|
||||
resource.watchdog_job()
|
||||
|
||||
return resource
|
||||
|
||||
else:
|
||||
|
@ -243,33 +235,15 @@ class Resource:
|
|||
# Create a resource for transmission to a remote destination
|
||||
# The data passed can be either a bytes-array or a file opened
|
||||
# in binary read mode.
|
||||
def __init__(self, data, link, metadata=None, advertise=True, auto_compress=True, callback=None, progress_callback=None,
|
||||
timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False, sent_metadata_size=0):
|
||||
|
||||
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
|
||||
data_size = None
|
||||
resource_data = None
|
||||
self.assembly_lock = False
|
||||
self.preparing_next_segment = False
|
||||
self.next_segment = None
|
||||
self.metadata = None
|
||||
self.has_metadata = False
|
||||
self.metadata_size = sent_metadata_size
|
||||
|
||||
if metadata != None:
|
||||
packed_metadata = umsgpack.packb(metadata)
|
||||
metadata_size = len(packed_metadata)
|
||||
if metadata_size > Resource.METADATA_MAX_SIZE:
|
||||
raise SystemError("Resource metadata size exceeded")
|
||||
else:
|
||||
self.metadata = struct.pack(">I", metadata_size)[1:] + packed_metadata
|
||||
self.metadata_size = len(self.metadata)
|
||||
self.has_metadata = True
|
||||
else:
|
||||
self.metadata = b""
|
||||
if sent_metadata_size > 0: self.has_metadata = True
|
||||
|
||||
if data != None:
|
||||
if not hasattr(data, "read") and self.metadata_size + len(data) > Resource.MAX_EFFICIENT_SIZE:
|
||||
if not hasattr(data, "read") and len(data) > Resource.MAX_EFFICIENT_SIZE:
|
||||
original_data = data
|
||||
data_size = len(original_data)
|
||||
data = tempfile.TemporaryFile()
|
||||
|
@ -277,43 +251,31 @@ class Resource:
|
|||
del original_data
|
||||
|
||||
if hasattr(data, "read"):
|
||||
if data_size == None: data_size = os.stat(data.name).st_size
|
||||
self.total_size = data_size + self.metadata_size
|
||||
if data_size == None:
|
||||
data_size = os.stat(data.name).st_size
|
||||
|
||||
if self.total_size <= Resource.MAX_EFFICIENT_SIZE:
|
||||
self.total_size = data_size
|
||||
|
||||
if data_size <= Resource.MAX_EFFICIENT_SIZE:
|
||||
self.total_segments = 1
|
||||
self.segment_index = 1
|
||||
self.split = False
|
||||
resource_data = data.read()
|
||||
data.close()
|
||||
|
||||
else:
|
||||
# self.total_segments = ((data_size-1)//Resource.MAX_EFFICIENT_SIZE)+1
|
||||
# self.segment_index = segment_index
|
||||
# self.split = True
|
||||
# seek_index = segment_index-1
|
||||
# seek_position = seek_index*Resource.MAX_EFFICIENT_SIZE
|
||||
|
||||
self.total_segments = ((self.total_size-1)//Resource.MAX_EFFICIENT_SIZE)+1
|
||||
self.total_segments = ((data_size-1)//Resource.MAX_EFFICIENT_SIZE)+1
|
||||
self.segment_index = segment_index
|
||||
self.split = True
|
||||
seek_index = segment_index-1
|
||||
first_read_size = Resource.MAX_EFFICIENT_SIZE - self.metadata_size
|
||||
|
||||
if segment_index == 1:
|
||||
seek_position = 0
|
||||
segment_read_size = first_read_size
|
||||
else:
|
||||
seek_position = first_read_size + ((seek_index-1)*Resource.MAX_EFFICIENT_SIZE)
|
||||
segment_read_size = Resource.MAX_EFFICIENT_SIZE
|
||||
seek_position = seek_index*Resource.MAX_EFFICIENT_SIZE
|
||||
|
||||
data.seek(seek_position)
|
||||
resource_data = data.read(segment_read_size)
|
||||
resource_data = data.read(Resource.MAX_EFFICIENT_SIZE)
|
||||
self.input_file = data
|
||||
|
||||
elif isinstance(data, bytes):
|
||||
data_size = len(data)
|
||||
self.total_size = data_size + self.metadata_size
|
||||
self.total_size = data_size
|
||||
|
||||
resource_data = data
|
||||
self.total_segments = 1
|
||||
|
@ -326,9 +288,7 @@ class Resource:
|
|||
else:
|
||||
raise TypeError("Invalid data instance type passed to resource initialisation")
|
||||
|
||||
if resource_data:
|
||||
if self.has_metadata: data = self.metadata + resource_data
|
||||
else: data = resource_data
|
||||
data = resource_data
|
||||
|
||||
self.status = Resource.NONE
|
||||
self.link = link
|
||||
|
@ -359,16 +319,7 @@ class Resource:
|
|||
self.request_id = request_id
|
||||
self.started_transferring = None
|
||||
self.is_response = is_response
|
||||
self.auto_compress_limit = Resource.AUTO_COMPRESS_MAX_SIZE
|
||||
self.auto_compress_option = auto_compress
|
||||
|
||||
if type(auto_compress) == bool:
|
||||
self.auto_compress = auto_compress
|
||||
elif type(auto_compress) == int:
|
||||
self.auto_compress = True
|
||||
self.auto_compress_limit = auto_compress
|
||||
else:
|
||||
raise TypeError(f"Invalid type {type(auto_compress)} for auto_compress option")
|
||||
|
||||
self.req_hashlist = []
|
||||
self.receiver_min_consecutive_height = 0
|
||||
|
@ -384,7 +335,7 @@ class Resource:
|
|||
self.uncompressed_data = data
|
||||
|
||||
compression_began = time.time()
|
||||
if self.auto_compress and data_size <= self.auto_compress_limit:
|
||||
if (auto_compress and len(self.uncompressed_data) <= Resource.AUTO_COMPRESS_MAX_SIZE):
|
||||
RNS.log("Compressing resource data...", RNS.LOG_EXTREME)
|
||||
self.compressed_data = bz2.compress(self.uncompressed_data)
|
||||
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_EXTREME)
|
||||
|
@ -403,20 +354,19 @@ class Resource:
|
|||
self.data += self.compressed_data
|
||||
|
||||
self.compressed = True
|
||||
self.uncompressed_data = None
|
||||
|
||||
else:
|
||||
self.data = b""
|
||||
self.data += RNS.Identity.get_random_hash()[:Resource.RANDOM_HASH_SIZE]
|
||||
self.data += self.uncompressed_data
|
||||
self.uncompressed_data = self.data
|
||||
|
||||
self.compressed = False
|
||||
self.compressed_data = None
|
||||
if self.auto_compress and data_size <= self.auto_compress_limit:
|
||||
if auto_compress:
|
||||
RNS.log("Compression did not decrease size, sending uncompressed", RNS.LOG_EXTREME)
|
||||
|
||||
self.compressed_data = None
|
||||
self.uncompressed_data = None
|
||||
|
||||
# Resources handle encryption directly to
|
||||
# make optimal use of packet MTU on an entire
|
||||
# encrypted stream. The Resource instance will
|
||||
|
@ -470,7 +420,6 @@ class Resource:
|
|||
|
||||
RNS.log("Hashmap computation concluded in "+str(round(time.time()-hashmap_computation_began, 3))+" seconds", RNS.LOG_EXTREME)
|
||||
|
||||
self.data = None
|
||||
if advertise:
|
||||
self.advertise()
|
||||
else:
|
||||
|
@ -555,7 +504,8 @@ class Resource:
|
|||
if self.link: self.link.expected_rate = self.eifr
|
||||
|
||||
def watchdog_job(self):
|
||||
thread = threading.Thread(target=self.__watchdog_job, daemon=True)
|
||||
thread = threading.Thread(target=self.__watchdog_job)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def __watchdog_job(self):
|
||||
|
@ -601,7 +551,6 @@ class Resource:
|
|||
else:
|
||||
sleep_time = self.last_activity + self.part_timeout_factor*((3*self.sdu)/self.eifr) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
|
||||
# TODO: Remove debug at some point
|
||||
# RNS.log(f"EIFR {RNS.prettyspeed(self.eifr)}, ETOF {RNS.prettyshorttime(expected_tof_remaining)} ", RNS.LOG_DEBUG, pt=True)
|
||||
# RNS.log(f"Resource ST {RNS.prettyshorttime(sleep_time)}, RTT {RNS.prettyshorttime(self.rtt or self.link.rtt)}, {self.outstanding_parts} left", RNS.LOG_DEBUG, pt=True)
|
||||
|
||||
|
@ -671,37 +620,29 @@ class Resource:
|
|||
self.status = Resource.ASSEMBLING
|
||||
stream = b"".join(self.parts)
|
||||
|
||||
if self.encrypted: data = self.link.decrypt(stream)
|
||||
else: data = stream
|
||||
if self.encrypted:
|
||||
data = self.link.decrypt(stream)
|
||||
else:
|
||||
data = stream
|
||||
|
||||
# Strip off random hash
|
||||
data = data[Resource.RANDOM_HASH_SIZE:]
|
||||
|
||||
if self.compressed: self.data = bz2.decompress(data)
|
||||
else: self.data = data
|
||||
if self.compressed:
|
||||
self.data = bz2.decompress(data)
|
||||
else:
|
||||
self.data = data
|
||||
|
||||
calculated_hash = RNS.Identity.full_hash(self.data+self.random_hash)
|
||||
if calculated_hash == self.hash:
|
||||
if self.has_metadata and self.segment_index == 1:
|
||||
# TODO: Add early metadata_ready callback
|
||||
metadata_size = self.data[0] << 16 | self.data[1] << 8 | self.data[2]
|
||||
packed_metadata = self.data[3:3+metadata_size]
|
||||
metadata_file = open(self.meta_storagepath, "wb")
|
||||
metadata_file.write(packed_metadata)
|
||||
metadata_file.close()
|
||||
del packed_metadata
|
||||
data = self.data[3+metadata_size:]
|
||||
else:
|
||||
data = self.data
|
||||
|
||||
if calculated_hash == self.hash:
|
||||
self.file = open(self.storagepath, "ab")
|
||||
self.file.write(data)
|
||||
self.file.write(self.data)
|
||||
self.file.close()
|
||||
self.status = Resource.COMPLETE
|
||||
del data
|
||||
self.prove()
|
||||
|
||||
else: self.status = Resource.CORRUPT
|
||||
else:
|
||||
self.status = Resource.CORRUPT
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
@ -713,27 +654,21 @@ class Resource:
|
|||
|
||||
if self.segment_index == self.total_segments:
|
||||
if self.callback != None:
|
||||
if not os.path.isfile(self.meta_storagepath):
|
||||
self.metadata = None
|
||||
else:
|
||||
metadata_file = open(self.meta_storagepath, "rb")
|
||||
self.metadata = umsgpack.unpackb(metadata_file.read())
|
||||
metadata_file.close()
|
||||
try: os.unlink(self.meta_storagepath)
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while cleaning up resource metadata file, the contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
self.data = open(self.storagepath, "rb")
|
||||
try: self.callback(self)
|
||||
try:
|
||||
self.callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource assembled callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
try:
|
||||
if hasattr(self.data, "close") and callable(self.data.close): self.data.close()
|
||||
if os.path.isfile(self.storagepath): os.unlink(self.storagepath)
|
||||
if hasattr(self.data, "close") and callable(self.data.close):
|
||||
self.data.close()
|
||||
|
||||
os.unlink(self.storagepath)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while cleaning up resource files, the contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log("Error while cleaning up resource files, the contained exception was:", RNS.LOG_ERROR)
|
||||
RNS.log(str(e))
|
||||
else:
|
||||
RNS.log("Resource segment "+str(self.segment_index)+" of "+str(self.total_segments)+" received, waiting for next segment to be announced", RNS.LOG_DEBUG)
|
||||
|
||||
|
@ -764,8 +699,7 @@ class Resource:
|
|||
request_id = self.request_id,
|
||||
is_response = self.is_response,
|
||||
advertise = False,
|
||||
auto_compress = self.auto_compress_option,
|
||||
sent_metadata_size = self.metadata_size,
|
||||
auto_compress = self.auto_compress,
|
||||
)
|
||||
|
||||
def validate_proof(self, proof_data):
|
||||
|
@ -778,18 +712,18 @@ class Resource:
|
|||
# If all segments were processed, we'll
|
||||
# signal that the resource sending concluded
|
||||
if self.callback != None:
|
||||
try: self.callback(self)
|
||||
except Exception as e: RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
try:
|
||||
self.callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
finally:
|
||||
try:
|
||||
if hasattr(self, "input_file"):
|
||||
if hasattr(self.input_file, "close") and callable(self.input_file.close): self.input_file.close()
|
||||
except Exception as e: RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
try:
|
||||
if hasattr(self, "input_file"):
|
||||
if hasattr(self.input_file, "close") and callable(self.input_file.close): self.input_file.close()
|
||||
except Exception as e: RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
|
||||
if hasattr(self.input_file, "close") and callable(self.input_file.close):
|
||||
self.input_file.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
# Otherwise we'll recursively create the
|
||||
# next segment of the resource
|
||||
|
@ -797,16 +731,8 @@ class Resource:
|
|||
RNS.log(f"Next segment preparation for resource {self} was not started yet, manually preparing now. This will cause transfer slowdown.", RNS.LOG_WARNING)
|
||||
self.__prepare_next_segment()
|
||||
|
||||
while self.next_segment == None: time.sleep(0.05)
|
||||
|
||||
self.data = None
|
||||
self.metadata = None
|
||||
self.parts = None
|
||||
self.input_file = None
|
||||
self.link = None
|
||||
self.req_hashlist = None
|
||||
self.hashmap = None
|
||||
|
||||
while self.next_segment == None:
|
||||
time.sleep(0.05)
|
||||
self.next_segment.advertise()
|
||||
else:
|
||||
pass
|
||||
|
@ -1268,7 +1194,6 @@ class ResourceAdvertisement:
|
|||
self.c = resource.compressed # Compression flag
|
||||
self.e = resource.encrypted # Encryption flag
|
||||
self.s = resource.split # Split flag
|
||||
self.x = resource.has_metadata # Metadata flag
|
||||
self.i = resource.segment_index # Segment index
|
||||
self.l = resource.total_segments # Total segments
|
||||
self.q = resource.request_id # ID of associated request
|
||||
|
@ -1284,7 +1209,7 @@ class ResourceAdvertisement:
|
|||
self.p = True
|
||||
|
||||
# Flags
|
||||
self.f = 0x00 | self.x << 5 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
|
||||
self.f = 0x00 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
|
||||
|
||||
def get_transfer_size(self):
|
||||
return self.t
|
||||
|
@ -1304,9 +1229,6 @@ class ResourceAdvertisement:
|
|||
def is_compressed(self):
|
||||
return self.c
|
||||
|
||||
def has_metadata(self):
|
||||
return self.x
|
||||
|
||||
def get_link(self):
|
||||
return self.link
|
||||
|
||||
|
@ -1356,6 +1278,5 @@ class ResourceAdvertisement:
|
|||
adv.s = True if ((adv.f >> 2) & 0x01) == 0x01 else False
|
||||
adv.u = True if ((adv.f >> 3) & 0x01) == 0x01 else False
|
||||
adv.p = True if ((adv.f >> 4) & 0x01) == 0x01 else False
|
||||
adv.x = True if ((adv.f >> 5) & 0x01) == 0x01 else False
|
||||
|
||||
return adv
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -211,8 +203,7 @@ class Reticulum:
|
|||
"""
|
||||
return Reticulum.__instance
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None,
|
||||
require_shared_instance=False, shared_instance_type=None):
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None, require_shared_instance=False):
|
||||
"""
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
|
@ -264,11 +255,9 @@ class Reticulum:
|
|||
self.local_control_port = 37429
|
||||
self.local_socket_path = None
|
||||
self.share_instance = True
|
||||
self.shared_instance_type = shared_instance_type
|
||||
self.rpc_listener = None
|
||||
self.rpc_key = None
|
||||
self.rpc_type = "AF_INET"
|
||||
self.use_af_unix = False
|
||||
|
||||
self.ifac_salt = Reticulum.IFAC_SALT
|
||||
|
||||
|
@ -327,9 +316,10 @@ class Reticulum:
|
|||
RNS.log(f"Configuration loaded from {self.configpath}", RNS.LOG_VERBOSE)
|
||||
|
||||
RNS.Identity.load_known_destinations()
|
||||
|
||||
RNS.Transport.start(self)
|
||||
|
||||
if self.use_af_unix:
|
||||
if RNS.vendor.platformutils.use_af_unix():
|
||||
self.rpc_addr = f"\0rns/{self.local_socket_path}/rpc"
|
||||
self.rpc_type = "AF_UNIX"
|
||||
else:
|
||||
|
@ -457,11 +447,7 @@ class Reticulum:
|
|||
if option == "instance_name":
|
||||
value = self.config["reticulum"][option]
|
||||
self.local_socket_path = value
|
||||
if option == "shared_instance_type":
|
||||
if self.shared_instance_type == None:
|
||||
value = self.config["reticulum"][option].lower()
|
||||
if value in ["tcp", "unix"]:
|
||||
self.shared_instance_type = value
|
||||
else: self.local_socket_path = "default"
|
||||
if option == "shared_instance_port":
|
||||
value = int(self.config["reticulum"][option])
|
||||
self.local_interface_port = value
|
||||
|
@ -520,17 +506,6 @@ class Reticulum:
|
|||
|
||||
if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG)
|
||||
else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG)
|
||||
|
||||
if RNS.vendor.platformutils.use_af_unix():
|
||||
if self.shared_instance_type == "tcp": self.use_af_unix = False
|
||||
else: self.use_af_unix = True
|
||||
else:
|
||||
self.shared_instance_type = "tcp"
|
||||
self.use_af_unix = False
|
||||
|
||||
if self.local_socket_path == None and self.use_af_unix:
|
||||
self.local_socket_path = "default"
|
||||
|
||||
self.__start_local_interface()
|
||||
|
||||
if self.is_shared_instance or self.is_standalone_instance:
|
||||
|
@ -1385,24 +1360,12 @@ share_instance = Yes
|
|||
|
||||
# If you want to run multiple *different* shared instances
|
||||
# on the same system, you will need to specify different
|
||||
# instance names for each. On platforms supporting domain
|
||||
# sockets, this can be done with the instance_name option:
|
||||
# shared instance ports for each. The defaults are given
|
||||
# below, and again, these options can be left out if you
|
||||
# don't need them.
|
||||
|
||||
instance_name = default
|
||||
|
||||
# Some platforms don't support domain sockets, and if that
|
||||
# is the case, you can isolate different instances by
|
||||
# specifying a unique set of ports for each:
|
||||
|
||||
# shared_instance_port = 37428
|
||||
# instance_control_port = 37429
|
||||
|
||||
|
||||
# If you want to explicitly use TCP for shared instance
|
||||
# communication, instead of domain sockets, this is also
|
||||
# possible, by using the following option:
|
||||
|
||||
# shared_instance_type = tcp
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
|
@ -1411,7 +1374,7 @@ instance_name = default
|
|||
# an optional directive, and can be left out for brevity.
|
||||
# This behaviour is disabled by default.
|
||||
|
||||
# panic_on_interface_error = No
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
[logging]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -1070,7 +1062,7 @@ class Transport:
|
|||
|
||||
if should_transmit:
|
||||
if not stored_hash:
|
||||
Transport.add_packet_hash(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
stored_hash = True
|
||||
|
||||
Transport.transmit(interface, packet.raw)
|
||||
|
@ -1082,16 +1074,11 @@ class Transport:
|
|||
Transport.jobs_locked = False
|
||||
return sent
|
||||
|
||||
@staticmethod
|
||||
def add_packet_hash(packet_hash):
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
Transport.packet_hashlist.add(packet_hash)
|
||||
|
||||
@staticmethod
|
||||
def packet_filter(packet):
|
||||
# If connected to a shared instance, it will handle
|
||||
# packet filtering
|
||||
if Transport.owner.is_connected_to_shared_instance: return True
|
||||
# TODO: Think long and hard about this.
|
||||
# Is it even strictly necessary with the current
|
||||
# transport rules?
|
||||
|
||||
# Filter packets intended for other transport instances
|
||||
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
|
||||
|
@ -1279,7 +1266,7 @@ class Transport:
|
|||
remember_packet_hash = False
|
||||
|
||||
if remember_packet_hash:
|
||||
Transport.add_packet_hash(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
# TODO: Enable when caching has been redesigned
|
||||
# Transport.cache(packet)
|
||||
|
||||
|
@ -1366,7 +1353,6 @@ class Transport:
|
|||
proof_timeout += now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops)
|
||||
|
||||
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
|
||||
mode = RNS.Link.mode_from_lr_packet(packet)
|
||||
nh_mtu = outbound_interface.HW_MTU
|
||||
if path_mtu:
|
||||
if outbound_interface.HW_MTU == None:
|
||||
|
@ -1380,7 +1366,7 @@ class Transport:
|
|||
else:
|
||||
if nh_mtu < path_mtu:
|
||||
path_mtu = nh_mtu
|
||||
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
|
||||
clamped_mtu = RNS.Link.mtu_bytes(path_mtu)
|
||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
|
||||
|
@ -1445,7 +1431,7 @@ class Transport:
|
|||
# Add this packet to the filter hashlist if we
|
||||
# have determined that it's actually our turn
|
||||
# to process it.
|
||||
Transport.add_packet_hash(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
|
@ -1834,7 +1820,6 @@ class Transport:
|
|||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
|
||||
mode = RNS.Link.mode_from_lr_packet(packet)
|
||||
if packet.receiving_interface.AUTOCONFIGURE_MTU or packet.receiving_interface.FIXED_MTU:
|
||||
nh_mtu = packet.receiving_interface.HW_MTU
|
||||
else:
|
||||
|
@ -1848,7 +1833,7 @@ class Transport:
|
|||
else:
|
||||
if nh_mtu < path_mtu:
|
||||
path_mtu = nh_mtu
|
||||
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
|
||||
clamped_mtu = RNS.Link.mtu_bytes(path_mtu)
|
||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
|
||||
|
@ -1886,7 +1871,8 @@ class Transport:
|
|||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
packet.destination = destination
|
||||
if destination.receive(packet):
|
||||
destination.receive(packet)
|
||||
|
||||
if destination.proof_strategy == RNS.Destination.PROVE_ALL:
|
||||
packet.prove()
|
||||
|
||||
|
@ -1909,15 +1895,15 @@ class Transport:
|
|||
if packet.receiving_interface == link_entry[IDX_LT_NH_IF]:
|
||||
try:
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2 or len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE:
|
||||
signalling_bytes = b""
|
||||
mtu_bytes = b""
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE:
|
||||
signalling_bytes = RNS.Link.signalling_bytes(RNS.Link.mtu_from_lp_packet(packet), RNS.Link.mode_from_lp_packet(packet))
|
||||
mtu_bytes = RNS.Link.mtu_bytes(RNS.Link.mtu_from_lp_packet(packet))
|
||||
|
||||
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2]
|
||||
peer_identity = RNS.Identity.recall(link_entry[IDX_LT_DSTHASH])
|
||||
peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE]
|
||||
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes+signalling_bytes
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes+mtu_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if peer_identity.validate(signature, signed_data):
|
||||
|
@ -1959,7 +1945,7 @@ class Transport:
|
|||
# Add this packet to the filter hashlist if we
|
||||
# have determined that it's actually destined
|
||||
# for this system, and then validate the proof
|
||||
Transport.add_packet_hash(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
link.validate_proof(packet)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
|
@ -2777,6 +2763,7 @@ class Transport:
|
|||
Transport.reverse_table = {}
|
||||
Transport.link_table = {}
|
||||
Transport.held_announces = {}
|
||||
Transport.announce_handlers = []
|
||||
Transport.tunnels = {}
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -33,7 +25,6 @@
|
|||
import RNS
|
||||
import argparse
|
||||
import threading
|
||||
import shutil
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
@ -43,7 +34,6 @@ from RNS._version import __version__
|
|||
APP_NAME = "rncp"
|
||||
allow_all = False
|
||||
allow_fetch = False
|
||||
allow_overwrite_on_receive = False
|
||||
fetch_auto_compress = True
|
||||
fetch_jail = None
|
||||
save_path = None
|
||||
|
@ -57,14 +47,13 @@ erase_str = "\33[2K\r"
|
|||
|
||||
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False,
|
||||
limit = None, disable_auth = None, fetch_allowed = False, no_compress=False,
|
||||
jail = None, save = None, announce = False, allow_overwrite=False):
|
||||
jail = None, save = None, announce = False):
|
||||
|
||||
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path
|
||||
global fetch_auto_compress, allow_overwrite_on_receive
|
||||
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path, fetch_auto_compress
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
allow_fetch = fetch_allowed
|
||||
fetch_auto_compress = not no_compress
|
||||
allow_overwrite_on_receive = allow_overwrite
|
||||
identity = None
|
||||
if announce < 0:
|
||||
announce = False
|
||||
|
@ -186,15 +175,22 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
|
|||
if target_link != None:
|
||||
RNS.log("Sending file "+str(file_path)+" to client", RNS.LOG_VERBOSE)
|
||||
|
||||
try:
|
||||
metadata = {"name": os.path.basename(file_path).encode("utf-8") }
|
||||
fetch_resource = RNS.Resource(open(file_path, "rb"), target_link, metadata=metadata, auto_compress=fetch_auto_compress)
|
||||
temp_file = TemporaryFile()
|
||||
real_file = open(file_path, "rb")
|
||||
filename_bytes = os.path.basename(file_path).encode("utf-8")
|
||||
filename_len = len(filename_bytes)
|
||||
|
||||
if filename_len > 0xFFFF:
|
||||
print("Filename exceeds max size, cannot send")
|
||||
RNS.exit(1)
|
||||
|
||||
temp_file.write(filename_len.to_bytes(2, "big"))
|
||||
temp_file.write(filename_bytes)
|
||||
temp_file.write(real_file.read())
|
||||
temp_file.seek(0)
|
||||
|
||||
fetch_resource = RNS.Resource(temp_file, target_link, auto_compress=fetch_auto_compress)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not send file to client. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -219,7 +215,8 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
|
|||
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
while True: time.sleep(1)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
def client_link_established(link):
|
||||
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
|
||||
|
@ -264,17 +261,14 @@ def receive_resource_started(resource):
|
|||
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
|
||||
|
||||
def receive_resource_concluded(resource):
|
||||
global save_path, allow_overwrite_on_receive
|
||||
global save_path
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
print(str(resource)+" completed")
|
||||
|
||||
if resource.metadata == None:
|
||||
print("Invalid data received, ignoring resource")
|
||||
return
|
||||
if resource.total_size > 4:
|
||||
filename_len = int.from_bytes(resource.data.read(2), "big")
|
||||
filename = resource.data.read(filename_len).decode("utf-8")
|
||||
|
||||
else:
|
||||
try:
|
||||
filename = os.path.basename(resource.metadata["name"].decode("utf-8"))
|
||||
counter = 0
|
||||
if save_path:
|
||||
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
|
||||
|
@ -285,21 +279,16 @@ def receive_resource_concluded(resource):
|
|||
saved_filename = filename
|
||||
|
||||
full_save_path = saved_filename
|
||||
if allow_overwrite_on_receive:
|
||||
if os.path.isfile(full_save_path):
|
||||
try: os.unlink(full_save_path)
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not overwrite existing file {full_save_path}, renaming instead", RNS.LOG_ERROR)
|
||||
|
||||
while os.path.isfile(full_save_path):
|
||||
counter += 1
|
||||
full_save_path = saved_filename+"."+str(counter)
|
||||
|
||||
shutil.move(resource.data.name, full_save_path)
|
||||
file = open(full_save_path, "wb")
|
||||
file.write(resource.data.read())
|
||||
file.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while saving received resource: {e}", RNS.LOG_ERROR)
|
||||
return
|
||||
else:
|
||||
print("Invalid data received, ignoring resource")
|
||||
|
||||
else:
|
||||
print("Resource failed")
|
||||
|
@ -345,11 +334,10 @@ def sender_progress(resource):
|
|||
resource_done = True
|
||||
|
||||
link = None
|
||||
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None, allow_overwrite=False):
|
||||
global current_resource, resource_done, link, speed, show_phy_rates, save_path, allow_overwrite_on_receive
|
||||
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None):
|
||||
global current_resource, resource_done, link, speed, show_phy_rates, save_path
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
show_phy_rates = phy_rates
|
||||
allow_overwrite_on_receive = allow_overwrite
|
||||
|
||||
if save:
|
||||
sp = os.path.abspath(os.path.expanduser(save))
|
||||
|
@ -485,40 +473,32 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
|||
|
||||
def fetch_resource_concluded(resource):
|
||||
nonlocal resource_resolved, resource_status
|
||||
global save_path, allow_overwrite_on_receive
|
||||
global save_path
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
if resource.metadata == None:
|
||||
print("Invalid data received, ignoring resource")
|
||||
return
|
||||
if resource.total_size > 4:
|
||||
filename_len = int.from_bytes(resource.data.read(2), "big")
|
||||
filename = resource.data.read(filename_len).decode("utf-8")
|
||||
|
||||
else:
|
||||
try:
|
||||
filename = os.path.basename(resource.metadata["name"].decode("utf-8"))
|
||||
counter = 0
|
||||
if save_path:
|
||||
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
|
||||
if not saved_filename.startswith(save_path+"/"):
|
||||
print(f"Invalid save path {saved_filename}, ignoring")
|
||||
return
|
||||
|
||||
else:
|
||||
saved_filename = filename
|
||||
|
||||
full_save_path = saved_filename
|
||||
if allow_overwrite_on_receive:
|
||||
if os.path.isfile(full_save_path):
|
||||
try: os.unlink(full_save_path)
|
||||
except Exception as e:
|
||||
print(f"Could not overwrite existing file {full_save_path}, renaming instead")
|
||||
|
||||
while os.path.isfile(full_save_path):
|
||||
counter += 1
|
||||
full_save_path = saved_filename+"."+str(counter)
|
||||
|
||||
shutil.move(resource.data.name, full_save_path)
|
||||
file = open(full_save_path, "wb")
|
||||
file.write(resource.data.read())
|
||||
file.close()
|
||||
resource_status = "completed"
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred while saving received resource: {e}")
|
||||
return
|
||||
else:
|
||||
print("Invalid data received, ignoring resource")
|
||||
resource_status = "invalid_data"
|
||||
|
||||
else:
|
||||
print("Resource failed")
|
||||
|
@ -616,6 +596,7 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
|||
|
||||
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, no_compress=False):
|
||||
global current_resource, resource_done, link, speed, show_phy_rates, phy_got_total, phy_speed
|
||||
from tempfile import TemporaryFile
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
show_phy_rates = phy_rates
|
||||
|
||||
|
@ -637,7 +618,21 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
|||
print("File not found")
|
||||
sys.exit(1)
|
||||
|
||||
metadata = {"name": os.path.basename(file_path).encode("utf-8") }
|
||||
temp_file = TemporaryFile()
|
||||
real_file = open(file_path, "rb")
|
||||
filename_bytes = os.path.basename(file_path).encode("utf-8")
|
||||
filename_len = len(filename_bytes)
|
||||
|
||||
if filename_len > 0xFFFF:
|
||||
print("Filename exceeds max size, cannot send")
|
||||
RNS.exit(1)
|
||||
else:
|
||||
print("Preparing file...", end=es)
|
||||
|
||||
temp_file.write(filename_len.to_bytes(2, "big"))
|
||||
temp_file.write(filename_bytes)
|
||||
temp_file.write(real_file.read())
|
||||
temp_file.seek(0)
|
||||
|
||||
print(f"{erase_str}", end="")
|
||||
|
||||
|
@ -724,12 +719,9 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
|||
|
||||
link.identify(identity)
|
||||
auto_compress = True
|
||||
if no_compress: auto_compress = False
|
||||
try: resource = RNS.Resource(open(file_path, "rb"), link, metadata=metadata, callback = sender_progress, progress_callback = sender_progress, auto_compress = auto_compress)
|
||||
except Exception as e:
|
||||
print(f"Could not start transfer: {e}")
|
||||
RNS.exit(1)
|
||||
|
||||
if no_compress:
|
||||
auto_compress = False
|
||||
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress, auto_compress = auto_compress)
|
||||
current_resource = resource
|
||||
|
||||
while resource.status < RNS.Resource.TRANSFERRING:
|
||||
|
@ -800,6 +792,8 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
|||
print("\n"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||
link.teardown()
|
||||
time.sleep(0.25)
|
||||
real_file.close()
|
||||
temp_file.close()
|
||||
RNS.exit(0)
|
||||
|
||||
def main():
|
||||
|
@ -817,7 +811,6 @@ def main():
|
|||
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
|
||||
parser.add_argument("-j", "--jail", metavar="path", action="store", default=None, help="restrict fetch requests to specified path", type=str)
|
||||
parser.add_argument("-s", "--save", metavar="path", action="store", default=None, help="save received files in specified path", type=str)
|
||||
parser.add_argument('-O', '--overwrite', action='store_true', default=False, help="Allow overwriting received files, instead of adding postfix")
|
||||
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
|
||||
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity (or add in ~/.rncp/allowed_identities)", type=str)
|
||||
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept requests from anyone")
|
||||
|
@ -843,7 +836,6 @@ def main():
|
|||
# limit=args.limit,
|
||||
disable_auth=args.no_auth,
|
||||
announce=args.b,
|
||||
allow_overwrite=args.overwrite,
|
||||
)
|
||||
|
||||
elif args.fetch:
|
||||
|
@ -858,7 +850,6 @@ def main():
|
|||
silent = args.silent,
|
||||
phy_rates = args.phy_rates,
|
||||
save = args.save,
|
||||
allow_overwrite=args.overwrite,
|
||||
)
|
||||
else:
|
||||
print("")
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2023-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2023-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2018-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
@ -117,24 +109,12 @@ share_instance = Yes
|
|||
|
||||
# If you want to run multiple *different* shared instances
|
||||
# on the same system, you will need to specify different
|
||||
# instance names for each. On platforms supporting domain
|
||||
# sockets, this can be done with the instance_name option:
|
||||
# shared instance ports for each. The defaults are given
|
||||
# below, and again, these options can be left out if you
|
||||
# don't need them.
|
||||
|
||||
instance_name = default
|
||||
|
||||
# Some platforms don't support domain sockets, and if that
|
||||
# is the case, you can isolate different instances by
|
||||
# specifying a unique set of ports for each:
|
||||
|
||||
# shared_instance_port = 37428
|
||||
# instance_control_port = 37429
|
||||
|
||||
|
||||
# If you want to explicitly use TCP for shared instance
|
||||
# communication, instead of domain sockets, this is also
|
||||
# possible, by using the following option:
|
||||
|
||||
# shared_instance_type = tcp
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# On systems where running instances may not have access
|
||||
|
@ -166,7 +146,7 @@ instance_name = default
|
|||
# an optional directive, and can be left out for brevity.
|
||||
# This behaviour is disabled by default.
|
||||
|
||||
# panic_on_interface_error = No
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
# When Transport is enabled, it is possible to allow the
|
||||
|
@ -177,7 +157,7 @@ instance_name = default
|
|||
# Transport Instance, and printed to the log at startup.
|
||||
# Optional, and disabled by default.
|
||||
|
||||
# respond_to_probes = No
|
||||
respond_to_probes = No
|
||||
|
||||
|
||||
[logging]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -11,16 +11,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2025 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
|
||||
|
@ -9,16 +9,8 @@
|
|||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
# 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,
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.9.6"
|
||||
__version__ = "0.9.4"
|
||||
|
|
30
RNS/vendor/platformutils.py
vendored
30
RNS/vendor/platformutils.py
vendored
|
@ -1,33 +1,3 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# 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 Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - 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.
|
||||
|
||||
def get_platform():
|
||||
from os import environ
|
||||
if "ANDROID_ARGUMENT" in environ: return "android"
|
||||
|
|
21
Roadmap.md
21
Roadmap.md
|
@ -14,6 +14,18 @@ This document outlines the currently established development roadmap for Reticul
|
|||
## Currently Active Work Areas
|
||||
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
|
||||
|
||||
- The current `0.8.x` release cycle aims at completing
|
||||
- [ ] Hot-pluggable interface system
|
||||
- [ ] External interface plugins
|
||||
- [ ] Network-wide path balancing and multi-pathing
|
||||
- [ ] Expanded hardware support
|
||||
- [ ] Overhauling and updating the documentation
|
||||
- [ ] Distributed Destination Naming System
|
||||
- [ ] A standalone RNS Daemon app for Android
|
||||
- [ ] Addding automatic retries to all use cases of the `Request` API
|
||||
- [ ] Performance and memory optimisations of the Python reference implementation
|
||||
- [ ] Fixing bugs discovered while operating Reticulum systems and applications
|
||||
|
||||
## Primary Efforts
|
||||
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
|
||||
|
||||
|
@ -38,14 +50,17 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
|||
### Universality
|
||||
These efforts seek to broaden the universality of the Reticulum software and hardware ecosystem by continously diversifying platform support, and by improving the overall availability and ease of deployment of the Reticulum stack.
|
||||
|
||||
- OpenWRT support
|
||||
- Create a standalone RNS Daemon app for Android
|
||||
- A lightweight and portable C implementation for microcontrollers, µRNS
|
||||
- A portable, high-performance Reticulum implementation in C/C++, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
|
||||
- Performance and memory optimisations of the Python implementation
|
||||
- Bindings for other programming languages
|
||||
|
||||
### Functionality
|
||||
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
|
||||
|
||||
- Add support for user-supplied external interface drivers
|
||||
- Add interface hot-plug and live up/down control to running instances
|
||||
- Add automatic retries to all use cases of the `Request` API
|
||||
- Network-wide path balancing
|
||||
|
@ -55,11 +70,11 @@ These efforts aim to expand and improve the core functionality and reliability o
|
|||
- [Metric-based path selection and multiple paths](https://github.com/markqvist/Reticulum/discussions/86)
|
||||
|
||||
### Usability & Utility
|
||||
These efforts seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
|
||||
These effors seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
|
||||
|
||||
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
|
||||
- Transit traffic display in `rnstatus`
|
||||
- `rnsconfig` utility
|
||||
- Transit traffic display in rnstatus
|
||||
- rnsconfig utility
|
||||
|
||||
### Interfaceability
|
||||
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: cce3deb193d6110f18b4ace0b3f4099b
|
||||
config: b499af51edc22529181ef3b25973fa2b
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
|
|
@ -22,9 +22,6 @@ Donations are gratefully accepted via the following channels:
|
|||
Bitcoin:
|
||||
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
|
||||
|
||||
Liberapay:
|
||||
https://liberapay.com/Reticulum/
|
||||
|
||||
Ko-Fi:
|
||||
https://ko-fi.com/markqvist
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
|
|||
public signing key.
|
||||
|
||||
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
|
||||
pre-shared AES-256 key associated with the destination. In case the packet is addressed to a *plain*
|
||||
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a *plain*
|
||||
destination type, the payload data will not be encrypted. Neither of these two destination types can offer
|
||||
forward secrecy. In general, it is recommended to always use the *single* destination type, unless it is
|
||||
strictly necessary to use one of the others.
|
||||
|
@ -858,17 +858,9 @@ of the different interface modes, and how they are configured.
|
|||
Cryptographic Primitives
|
||||
------------------------
|
||||
|
||||
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic
|
||||
primitives, with widely available implementations that can be used both on
|
||||
general-purpose CPUs and on microcontrollers.
|
||||
|
||||
One of the primary considerations for choosing this particular set of primitives is
|
||||
that they can be implemented *safely* with relatively few pitfalls, on practically
|
||||
all current computing platforms.
|
||||
|
||||
The primitives listed here **are authoritative**. Anything claiming to be Reticulum,
|
||||
but not using these exact primitives **is not** Reticulum, and possibly an
|
||||
intentionally compromised or weakened clone. The utilised primitives are:
|
||||
Reticulum has been designed to use a simple suite of efficient, strong and modern
|
||||
cryptographic primitives, with widely available implementations that can be used
|
||||
both on general-purpose CPUs and on microcontrollers. The necessary primitives are:
|
||||
|
||||
* Ed25519 for signatures
|
||||
|
||||
|
@ -880,11 +872,11 @@ intentionally compromised or weakened clone. The utilised primitives are:
|
|||
|
||||
* Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-256 in CBC mode with PKCS7 padding
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
* HMAC using SHA256 for message authentication
|
||||
|
||||
* IVs must be generated through ``os.urandom()`` or better
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* No Fernet version and timestamp metadata fields
|
||||
|
||||
|
@ -892,7 +884,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
|
|||
|
||||
* SHA-512
|
||||
|
||||
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-256-CBC``
|
||||
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-128-CBC``
|
||||
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
|
||||
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
|
||||
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
|
||||
|
|
|
@ -69,12 +69,12 @@ configuration file is created. The default configuration looks like this:
|
|||
|
||||
# If you enable Transport, your system will route traffic
|
||||
# for other peers, pass announces and serve path requests.
|
||||
# This should be done for systems that are suited to act
|
||||
# as transport nodes, ie. if they are stationary and
|
||||
# This should only be done for systems that are suited to
|
||||
# act as transport nodes, ie. if they are stationary and
|
||||
# always-on. This directive is optional and can be removed
|
||||
# for brevity.
|
||||
|
||||
enable_transport = No
|
||||
enable_transport = False
|
||||
|
||||
|
||||
# By default, the first program to launch the Reticulum
|
||||
|
@ -91,24 +91,12 @@ configuration file is created. The default configuration looks like this:
|
|||
|
||||
# If you want to run multiple *different* shared instances
|
||||
# on the same system, you will need to specify different
|
||||
# instance names for each. On platforms supporting domain
|
||||
# sockets, this can be done with the instance_name option:
|
||||
# shared instance ports for each. The defaults are given
|
||||
# below, and again, these options can be left out if you
|
||||
# don't need them.
|
||||
|
||||
instance_name = default
|
||||
|
||||
# Some platforms don't support domain sockets, and if that
|
||||
# is the case, you can isolate different instances by
|
||||
# specifying a unique set of ports for each:
|
||||
|
||||
# shared_instance_port = 37428
|
||||
# instance_control_port = 37429
|
||||
|
||||
|
||||
# If you want to explicitly use TCP for shared instance
|
||||
# communication, instead of domain sockets, this is also
|
||||
# possible, by using the following option:
|
||||
|
||||
# shared_instance_type = tcp
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# On systems where running instances may not have access
|
||||
|
@ -122,25 +110,13 @@ configuration file is created. The default configuration looks like this:
|
|||
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
|
||||
|
||||
|
||||
# It is possible to allow remote management of Reticulum
|
||||
# systems using the various built-in utilities, such as
|
||||
# rnstatus and rnpath. You will need to specify one or
|
||||
# more Reticulum Identity hashes for authenticating the
|
||||
# queries from client programs. For this purpose, you can
|
||||
# use existing identity files, or generate new ones with
|
||||
# the rnid utility.
|
||||
|
||||
# enable_remote_management = yes
|
||||
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
# an optional directive, and can be left out for brevity.
|
||||
# This behaviour is disabled by default.
|
||||
|
||||
# panic_on_interface_error = No
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
# When Transport is enabled, it is possible to allow the
|
||||
|
@ -151,7 +127,7 @@ configuration file is created. The default configuration looks like this:
|
|||
# Transport Instance, and printed to the log at startup.
|
||||
# Optional, and disabled by default.
|
||||
|
||||
# respond_to_probes = No
|
||||
respond_to_probes = No
|
||||
|
||||
|
||||
[logging]
|
||||
|
|
|
@ -68,7 +68,7 @@ What does Reticulum Offer?
|
|||
|
||||
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-256 in CBC mode with PKCS7 padding
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
* HMAC using SHA256 for authentication
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.9.6 beta',
|
||||
VERSION: '0.9.4 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -1610,8 +1610,8 @@ the link has been established.</p>
|
|||
<span id="example-request"></span><h2>Requests & Responses<a class="headerlink" href="#requests-responses" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Request</em> example explores sending requests and receiving responses.</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 perform requests #</span>
|
||||
<span class="c1"># and receive responses over a link. #</span>
|
||||
<span class="c1"># This RNS example demonstrates how to set perform #</span>
|
||||
<span class="c1"># requests and receive responses over a link. #</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
|
@ -3244,7 +3244,9 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
|||
Any number of custom interfaces can be loaded and utilised by Reticulum, and
|
||||
will be fully on-par with natively included interfaces, including all supported
|
||||
<a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">interface modes</span></a> and <a class="reference internal" href="interfaces.html#interfaces-options"><span class="std std-ref">common configuration options</span></a>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example illustrates creating a custom interface</span>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># MIT License - Copyright (c) 2024 Mark Qvist / unsigned.io</span>
|
||||
|
||||
<span class="c1"># This example illustrates creating a custom interface</span>
|
||||
<span class="c1"># definition, that can be loaded and used by Reticulum at</span>
|
||||
<span class="c1"># runtime. Any number of custom interfaces can be created</span>
|
||||
<span class="c1"># and loaded. To use the interface place it in the folder</span>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -139,7 +139,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -165,7 +165,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -389,8 +389,6 @@
|
|||
<li><a href="reference.html#RNS.Reticulum.get_instance">get_instance() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_mdu">get_mdu() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_mode">get_mode() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_mtu">get_mtu() (RNS.Link method)</a>
|
||||
</li>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Configuring Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Configuring Interfaces" href="interfaces.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
Binary file not shown.
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>API Reference - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -228,7 +228,7 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
|
|||
<p id="api-reticulum"><h3> Reticulum </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">require_shared_instance</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">shared_instance_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">require_shared_instance</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>This class is used to initialise access to Reticulum within a
|
||||
program. You must create exactly one instance of this class before
|
||||
carrying out any other RNS operations, such as creating destinations
|
||||
|
@ -817,7 +817,7 @@ proofs should be returned for received packets.</p>
|
|||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.register_request_handler">
|
||||
<span class="sig-name descname"><span class="pre">register_request_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">response_generator</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allow</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">ALLOW_NONE</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allowed_list</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">auto_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.register_request_handler" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">register_request_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">response_generator</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allow</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">ALLOW_NONE</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allowed_list</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.register_request_handler" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Registers a request handler.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
|
@ -826,7 +826,6 @@ proofs should be returned for received packets.</p>
|
|||
<li><p><strong>response_generator</strong> – A function or method with the signature <em>response_generator(path, data, request_id, link_id, remote_identity, requested_at)</em> to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns <code class="docutils literal notranslate"><span class="pre">None</span></code>, no response will be sent.</p></li>
|
||||
<li><p><strong>allow</strong> – One of <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_NONE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_ALL</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code>. If <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code> is set, the request handler will only respond to requests for identified peers in the supplied list.</p></li>
|
||||
<li><p><strong>allowed_list</strong> – A list of <em>bytes-like</em> <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> hashes.</p></li>
|
||||
<li><p><strong>auto_compress</strong> – If <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">False</span></code>, determines whether automatic compression of responses should be carried out. If set to an integer value, responses will only be auto-compressed if under this size in bytes. If omitted, the default compression settings will be followed.</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Raises<span class="colon">:</span></dt>
|
||||
|
@ -1016,9 +1015,9 @@ they are addressed to a <code class="docutils literal notranslate"><span class="
|
|||
<code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> destination or a <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a>.</p>
|
||||
<p>For <code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> destinations, Reticulum will use the
|
||||
pre-shared key configured for the destination. All packets to group
|
||||
destinations are encrypted with the same AES-256 key.</p>
|
||||
destinations are encrypted with the same AES-128 key.</p>
|
||||
<p>For <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code> destinations, Reticulum will use a newly
|
||||
derived ephemeral AES-256 key for every packet.</p>
|
||||
derived ephemeral AES-128 key for every packet.</p>
|
||||
<p>For <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a> destinations, Reticulum will use per-link
|
||||
ephemeral keys, and offers <strong>Forward Secrecy</strong>.</p>
|
||||
<dl class="field-list simple">
|
||||
|
@ -1195,14 +1194,14 @@ and encrypted connectivity with the specified destination.</p>
|
|||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Link.STALE_GRACE">
|
||||
<span class="sig-name descname"><span class="pre">STALE_GRACE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.STALE_GRACE" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">STALE_GRACE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2</span></em><a class="headerlink" href="#RNS.Link.STALE_GRACE" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Grace period in seconds used in link timeout calculation.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Link.KEEPALIVE">
|
||||
<span class="sig-name descname"><span class="pre">KEEPALIVE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">360</span></em><a class="headerlink" href="#RNS.Link.KEEPALIVE" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Default interval for sending keep-alive packets on established links in seconds.</p>
|
||||
<dd><p>Interval for sending keep-alive packets on established links in seconds.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
|
@ -1333,16 +1332,6 @@ statistics, you will be able to retrieve stats such as <em>RSSI</em>, <em>SNR</e
|
|||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.get_mode">
|
||||
<span class="sig-name descname"><span class="pre">get_mode</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_mode" title="Permalink to this definition">#</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p>The mode of an established link.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.get_age">
|
||||
<span class="sig-name descname"><span class="pre">get_age</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_age" title="Permalink to this definition">#</a></dt>
|
||||
|
@ -2258,7 +2247,6 @@ will announce it.</p>
|
|||
<li><a class="reference internal" href="#RNS.Link.get_mtu"><code class="docutils literal notranslate"><span class="pre">get_mtu()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_mdu"><code class="docutils literal notranslate"><span class="pre">get_mdu()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_expected_rate"><code class="docutils literal notranslate"><span class="pre">get_expected_rate()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_mode"><code class="docutils literal notranslate"><span class="pre">get_mode()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_age"><code class="docutils literal notranslate"><span class="pre">get_age()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.no_inbound_for"><code class="docutils literal notranslate"><span class="pre">no_inbound_for()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.no_outbound_for"><code class="docutils literal notranslate"><span class="pre">no_outbound_for()</span></code></a></li>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.9.6 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.9.4 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
|
@ -138,7 +138,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -164,7 +164,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -237,9 +237,6 @@ Ethereum:
|
|||
Bitcoin:
|
||||
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
|
||||
|
||||
Liberapay:
|
||||
https://liberapay.com/Reticulum/
|
||||
|
||||
Ko-Fi:
|
||||
https://ko-fi.com/markqvist
|
||||
</pre></div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -673,7 +673,7 @@ public signing key.</div>
|
|||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">In case the packet is addressed to a <em>group</em> destination type, the packet will be encrypted with the
|
||||
pre-shared AES-256 key associated with the destination. In case the packet is addressed to a <em>plain</em>
|
||||
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a <em>plain</em>
|
||||
destination type, the payload data will not be encrypted. Neither of these two destination types can offer
|
||||
forward secrecy. In general, it is recommended to always use the <em>single</em> destination type, unless it is
|
||||
strictly necessary to use one of the others.</div>
|
||||
|
@ -1062,15 +1062,9 @@ of the different interface modes, and how they are configured.</p>
|
|||
</section>
|
||||
<section id="cryptographic-primitives">
|
||||
<span id="understanding-primitives"></span><h3>Cryptographic Primitives<a class="headerlink" href="#cryptographic-primitives" title="Permalink to this heading">#</a></h3>
|
||||
<p>Reticulum uses a simple suite of efficient, strong and well-tested cryptographic
|
||||
primitives, with widely available implementations that can be used both on
|
||||
general-purpose CPUs and on microcontrollers.</p>
|
||||
<p>One of the primary considerations for choosing this particular set of primitives is
|
||||
that they can be implemented <em>safely</em> with relatively few pitfalls, on practically
|
||||
all current computing platforms.</p>
|
||||
<p>The primitives listed here <strong>are authoritative</strong>. Anything claiming to be Reticulum,
|
||||
but not using these exact primitives <strong>is not</strong> Reticulum, and possibly an
|
||||
intentionally compromised or weakened clone. The utilised primitives are:</p>
|
||||
<p>Reticulum has been designed to use a simple suite of efficient, strong and modern
|
||||
cryptographic primitives, with widely available implementations that can be used
|
||||
both on general-purpose CPUs and on microcontrollers. The necessary primitives are:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Ed25519 for signatures</p></li>
|
||||
<li><p>X25519 for ECDH key exchanges</p></li>
|
||||
|
@ -1078,16 +1072,16 @@ intentionally compromised or weakened clone. The utilised primitives are:</p>
|
|||
<li><p>Encrypted tokens are based on the Fernet spec</p>
|
||||
<ul>
|
||||
<li><p>Ephemeral keys derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<li><p>AES-256 in CBC mode with PKCS7 padding</p></li>
|
||||
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
|
||||
<li><p>HMAC using SHA256 for message authentication</p></li>
|
||||
<li><p>IVs must be generated through <code class="docutils literal notranslate"><span class="pre">os.urandom()</span></code> or better</p></li>
|
||||
<li><p>IVs are generated through os.urandom()</p></li>
|
||||
<li><p>No Fernet version and timestamp metadata fields</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>SHA-256</p></li>
|
||||
<li><p>SHA-512</p></li>
|
||||
</ul>
|
||||
<p>In the default installation configuration, the <code class="docutils literal notranslate"><span class="pre">X25519</span></code>, <code class="docutils literal notranslate"><span class="pre">Ed25519</span></code> and <code class="docutils literal notranslate"><span class="pre">AES-256-CBC</span></code>
|
||||
<p>In the default installation configuration, the <code class="docutils literal notranslate"><span class="pre">X25519</span></code>, <code class="docutils literal notranslate"><span class="pre">Ed25519</span></code> and <code class="docutils literal notranslate"><span class="pre">AES-128-CBC</span></code>
|
||||
primitives are provided by <a class="reference external" href="https://www.openssl.org/">OpenSSL</a> (via the <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>
|
||||
package). The hashing functions <code class="docutils literal notranslate"><span class="pre">SHA-256</span></code> and <code class="docutils literal notranslate"><span class="pre">SHA-512</span></code> are provided by the standard
|
||||
Python <a class="reference external" href="https://docs.python.org/3/library/hashlib.html">hashlib</a>. The <code class="docutils literal notranslate"><span class="pre">HKDF</span></code>, <code class="docutils literal notranslate"><span class="pre">HMAC</span></code>,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -277,12 +277,12 @@ configuration file is created. The default configuration looks like this:</p>
|
|||
|
||||
<span class="c1"># If you enable Transport, your system will route traffic</span>
|
||||
<span class="c1"># for other peers, pass announces and serve path requests.</span>
|
||||
<span class="c1"># This should be done for systems that are suited to act</span>
|
||||
<span class="c1"># as transport nodes, ie. if they are stationary and</span>
|
||||
<span class="c1"># This should only be done for systems that are suited to</span>
|
||||
<span class="c1"># act as transport nodes, ie. if they are stationary and</span>
|
||||
<span class="c1"># always-on. This directive is optional and can be removed</span>
|
||||
<span class="c1"># for brevity.</span>
|
||||
|
||||
<span class="n">enable_transport</span> <span class="o">=</span> <span class="n">No</span>
|
||||
<span class="n">enable_transport</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
|
||||
<span class="c1"># By default, the first program to launch the Reticulum</span>
|
||||
|
@ -299,24 +299,12 @@ configuration file is created. The default configuration looks like this:</p>
|
|||
|
||||
<span class="c1"># If you want to run multiple *different* shared instances</span>
|
||||
<span class="c1"># on the same system, you will need to specify different</span>
|
||||
<span class="c1"># instance names for each. On platforms supporting domain</span>
|
||||
<span class="c1"># sockets, this can be done with the instance_name option:</span>
|
||||
<span class="c1"># shared instance ports for each. The defaults are given</span>
|
||||
<span class="c1"># below, and again, these options can be left out if you</span>
|
||||
<span class="c1"># don't need them.</span>
|
||||
|
||||
<span class="n">instance_name</span> <span class="o">=</span> <span class="n">default</span>
|
||||
|
||||
<span class="c1"># Some platforms don't support domain sockets, and if that</span>
|
||||
<span class="c1"># is the case, you can isolate different instances by</span>
|
||||
<span class="c1"># specifying a unique set of ports for each:</span>
|
||||
|
||||
<span class="c1"># shared_instance_port = 37428</span>
|
||||
<span class="c1"># instance_control_port = 37429</span>
|
||||
|
||||
|
||||
<span class="c1"># If you want to explicitly use TCP for shared instance</span>
|
||||
<span class="c1"># communication, instead of domain sockets, this is also</span>
|
||||
<span class="c1"># possible, by using the following option:</span>
|
||||
|
||||
<span class="c1"># shared_instance_type = tcp</span>
|
||||
<span class="n">shared_instance_port</span> <span class="o">=</span> <span class="mi">37428</span>
|
||||
<span class="n">instance_control_port</span> <span class="o">=</span> <span class="mi">37429</span>
|
||||
|
||||
|
||||
<span class="c1"># On systems where running instances may not have access</span>
|
||||
|
@ -330,25 +318,13 @@ configuration file is created. The default configuration looks like this:</p>
|
|||
<span class="c1"># rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790</span>
|
||||
|
||||
|
||||
<span class="c1"># It is possible to allow remote management of Reticulum</span>
|
||||
<span class="c1"># systems using the various built-in utilities, such as</span>
|
||||
<span class="c1"># rnstatus and rnpath. You will need to specify one or</span>
|
||||
<span class="c1"># more Reticulum Identity hashes for authenticating the</span>
|
||||
<span class="c1"># queries from client programs. For this purpose, you can</span>
|
||||
<span class="c1"># use existing identity files, or generate new ones with</span>
|
||||
<span class="c1"># the rnid utility.</span>
|
||||
|
||||
<span class="c1"># enable_remote_management = yes</span>
|
||||
<span class="c1"># remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc</span>
|
||||
|
||||
|
||||
<span class="c1"># You can configure Reticulum to panic and forcibly close</span>
|
||||
<span class="c1"># if an unrecoverable interface error occurs, such as the</span>
|
||||
<span class="c1"># hardware device for an interface disappearing. This is</span>
|
||||
<span class="c1"># an optional directive, and can be left out for brevity.</span>
|
||||
<span class="c1"># This behaviour is disabled by default.</span>
|
||||
|
||||
<span class="c1"># panic_on_interface_error = No</span>
|
||||
<span class="n">panic_on_interface_error</span> <span class="o">=</span> <span class="n">No</span>
|
||||
|
||||
|
||||
<span class="c1"># When Transport is enabled, it is possible to allow the</span>
|
||||
|
@ -359,7 +335,7 @@ configuration file is created. The default configuration looks like this:</p>
|
|||
<span class="c1"># Transport Instance, and printed to the log at startup.</span>
|
||||
<span class="c1"># Optional, and disabled by default.</span>
|
||||
|
||||
<span class="c1"># respond_to_probes = No</span>
|
||||
<span class="n">respond_to_probes</span> <span class="o">=</span> <span class="n">No</span>
|
||||
|
||||
|
||||
<span class="p">[</span><span class="n">logging</span><span class="p">]</span>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.9.6 beta documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.9.4 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
|
@ -141,7 +141,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.4 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
|
@ -167,7 +167,7 @@
|
|||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.4 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
@ -279,7 +279,7 @@ considered complete and stable at the moment, but could change if absolutely war
|
|||
<li><p>Reticulum uses the following format for encrypted tokens:</p>
|
||||
<ul>
|
||||
<li><p>Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<li><p>AES-256 in CBC mode with PKCS7 padding</p></li>
|
||||
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
|
||||
<li><p>HMAC using SHA256 for authentication</p></li>
|
||||
<li><p>IVs are generated through os.urandom()</p></li>
|
||||
</ul>
|
||||
|
|
|
@ -22,9 +22,6 @@ Donations are gratefully accepted via the following channels:
|
|||
Bitcoin:
|
||||
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
|
||||
|
||||
Liberapay:
|
||||
https://liberapay.com/Reticulum/
|
||||
|
||||
Ko-Fi:
|
||||
https://ko-fi.com/markqvist
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
|
|||
public signing key.
|
||||
|
||||
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
|
||||
pre-shared AES-256 key associated with the destination. In case the packet is addressed to a *plain*
|
||||
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a *plain*
|
||||
destination type, the payload data will not be encrypted. Neither of these two destination types can offer
|
||||
forward secrecy. In general, it is recommended to always use the *single* destination type, unless it is
|
||||
strictly necessary to use one of the others.
|
||||
|
@ -858,17 +858,9 @@ of the different interface modes, and how they are configured.
|
|||
Cryptographic Primitives
|
||||
------------------------
|
||||
|
||||
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic
|
||||
primitives, with widely available implementations that can be used both on
|
||||
general-purpose CPUs and on microcontrollers.
|
||||
|
||||
One of the primary considerations for choosing this particular set of primitives is
|
||||
that they can be implemented *safely* with relatively few pitfalls, on practically
|
||||
all current computing platforms.
|
||||
|
||||
The primitives listed here **are authoritative**. Anything claiming to be Reticulum,
|
||||
but not using these exact primitives **is not** Reticulum, and possibly an
|
||||
intentionally compromised or weakened clone. The utilised primitives are:
|
||||
Reticulum has been designed to use a simple suite of efficient, strong and modern
|
||||
cryptographic primitives, with widely available implementations that can be used
|
||||
both on general-purpose CPUs and on microcontrollers. The necessary primitives are:
|
||||
|
||||
* Ed25519 for signatures
|
||||
|
||||
|
@ -880,11 +872,11 @@ intentionally compromised or weakened clone. The utilised primitives are:
|
|||
|
||||
* Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-256 in CBC mode with PKCS7 padding
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
* HMAC using SHA256 for message authentication
|
||||
|
||||
* IVs must be generated through ``os.urandom()`` or better
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* No Fernet version and timestamp metadata fields
|
||||
|
||||
|
@ -892,7 +884,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
|
|||
|
||||
* SHA-512
|
||||
|
||||
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-256-CBC``
|
||||
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-128-CBC``
|
||||
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
|
||||
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
|
||||
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
|
||||
|
|
|
@ -69,12 +69,12 @@ configuration file is created. The default configuration looks like this:
|
|||
|
||||
# If you enable Transport, your system will route traffic
|
||||
# for other peers, pass announces and serve path requests.
|
||||
# This should be done for systems that are suited to act
|
||||
# as transport nodes, ie. if they are stationary and
|
||||
# This should only be done for systems that are suited to
|
||||
# act as transport nodes, ie. if they are stationary and
|
||||
# always-on. This directive is optional and can be removed
|
||||
# for brevity.
|
||||
|
||||
enable_transport = No
|
||||
enable_transport = False
|
||||
|
||||
|
||||
# By default, the first program to launch the Reticulum
|
||||
|
@ -91,24 +91,12 @@ configuration file is created. The default configuration looks like this:
|
|||
|
||||
# If you want to run multiple *different* shared instances
|
||||
# on the same system, you will need to specify different
|
||||
# instance names for each. On platforms supporting domain
|
||||
# sockets, this can be done with the instance_name option:
|
||||
# shared instance ports for each. The defaults are given
|
||||
# below, and again, these options can be left out if you
|
||||
# don't need them.
|
||||
|
||||
instance_name = default
|
||||
|
||||
# Some platforms don't support domain sockets, and if that
|
||||
# is the case, you can isolate different instances by
|
||||
# specifying a unique set of ports for each:
|
||||
|
||||
# shared_instance_port = 37428
|
||||
# instance_control_port = 37429
|
||||
|
||||
|
||||
# If you want to explicitly use TCP for shared instance
|
||||
# communication, instead of domain sockets, this is also
|
||||
# possible, by using the following option:
|
||||
|
||||
# shared_instance_type = tcp
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# On systems where running instances may not have access
|
||||
|
@ -122,25 +110,13 @@ configuration file is created. The default configuration looks like this:
|
|||
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
|
||||
|
||||
|
||||
# It is possible to allow remote management of Reticulum
|
||||
# systems using the various built-in utilities, such as
|
||||
# rnstatus and rnpath. You will need to specify one or
|
||||
# more Reticulum Identity hashes for authenticating the
|
||||
# queries from client programs. For this purpose, you can
|
||||
# use existing identity files, or generate new ones with
|
||||
# the rnid utility.
|
||||
|
||||
# enable_remote_management = yes
|
||||
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
# an optional directive, and can be left out for brevity.
|
||||
# This behaviour is disabled by default.
|
||||
|
||||
# panic_on_interface_error = No
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
# When Transport is enabled, it is possible to allow the
|
||||
|
@ -151,7 +127,7 @@ configuration file is created. The default configuration looks like this:
|
|||
# Transport Instance, and printed to the log at startup.
|
||||
# Optional, and disabled by default.
|
||||
|
||||
# respond_to_probes = No
|
||||
respond_to_probes = No
|
||||
|
||||
|
||||
[logging]
|
||||
|
|
|
@ -68,7 +68,7 @@ What does Reticulum Offer?
|
|||
|
||||
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-256 in CBC mode with PKCS7 padding
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
* HMAC using SHA256 for authentication
|
||||
|
||||
|
|
4
setup.py
4
setup.py
|
@ -34,12 +34,10 @@ setuptools.setup(
|
|||
long_description_content_type="text/markdown",
|
||||
url="https://reticulum.network/",
|
||||
packages=packages,
|
||||
license="Reticulum License",
|
||||
license_files = ("LICENSE"),
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Development Status :: 4 - Beta",
|
||||
],
|
||||
entry_points= {
|
||||
'console_scripts': [
|
||||
|
|
|
@ -8,7 +8,7 @@ signed_message = "e51a008b8b8ba855993d8892a40daad84a6fb69a7138e1b5f69b427fe03449
|
|||
sig_from_key_0 = "3020ef58f861591826a61c3d2d4a25b949cdb3094085ba6b1177a6f2a05f3cdd24d1095d6fdd078f0b2826e80b261c93c1ff97fbfd4857f25706d57dd073590c"
|
||||
|
||||
encrypted_message = "71884a271ead43558fcf1e331c5aebcd43498f16da16f8056b0893ce6b15d521eaa4f31639cd34da1b57995944076c4f14f300f2d2612111d21a3429a9966ac1da68545c00c7887d8b26f6c1ab9defa020b9519849ca41b7904199882802b6542771df85144a79890289d3c02daef6c26652c5ce9de231a2"
|
||||
fixed_token = "e37705f9b432d3711acf028678b0b9d37fdf7e00a3b47c95251aad61447df2620b5b9978783c3d9f2fb762e68c8b57c554928fb70dd79c1033ce5865f91761aad3e992790f63456092cb69b7b045f539147f7ba10d480e300f193576ae2d75a7884809b76bd17e05a735383305c0aa5621395bbf51e8cc66c1c536f339f2bea600f08f8f9a76564b2522cd904b6c2b6e553ec3d4df718ae70434c734297b313539338d184d2c64a9c4ddbc9b9a4947d0b45f5a274f65ae9f6bb203562fd5cede6abd3c615b699156e08fa33b841647a0"
|
||||
fixed_token = "54d6ba347f3f2fe74fa52d6844a9090c049a6f437d7d151b9bd7db3e6785dd40286c451babda82660cbb4827517365b740675adf60d4b82778d7f7815a0e9818f2f2d3f15c0365e9d4f08df4f8261e5549c8c398e92bc66750fcd4ce7ea150f8a8761936341129e89afd22eaa57c303ccbe045d0b2fc7b8637946e16627419ef1fea0a0fef974c418a98af046d61e8e064f42c4948b0c81701106583c8f224329c0b475cb2168dc2e3fbf649edb79c58b7c839a509e146ec8d26589cb990c76c756fdefd0110410a6ab84fa3a722db74"
|
||||
|
||||
fixed_keys = [
|
||||
("f8953ffaf607627e615603ff1530c82c434cf87c07179dd7689ea776f30b964cfb7ba6164af00c5111a45e69e57d885e1285f8dbfe3a21e95ae17cf676b0f8b7", "650b5d76b6bec0390d1f8cfca5bd33f9"),
|
||||
|
@ -148,12 +148,9 @@ class TestIdentity(unittest.TestCase):
|
|||
def test_2_encrypt(self):
|
||||
print("")
|
||||
|
||||
# Test loading identity from bytes
|
||||
# Test decryption of known token
|
||||
fid = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
self.assertEqual(fid.hash, bytes.fromhex(fixed_keys[0][1]))
|
||||
|
||||
# Test decryption of known AES-256 token
|
||||
print("Testing decryption of known token")
|
||||
plaintext = fid.decrypt(bytes.fromhex(fixed_token))
|
||||
self.assertEqual(plaintext, bytes.fromhex(encrypted_message))
|
||||
|
||||
|
|
269
tests/link.py
269
tests/link.py
|
@ -1,6 +1,3 @@
|
|||
# import tracemalloc
|
||||
# tracemalloc.start()
|
||||
|
||||
import unittest
|
||||
|
||||
import subprocess
|
||||
|
@ -28,7 +25,6 @@ fixed_keys = [
|
|||
]
|
||||
|
||||
BUFFER_TEST_TARGET = 32000
|
||||
LINK_UP_WAIT = 0.050
|
||||
|
||||
def targets_job(caller):
|
||||
cmd = "python -c \"from tests.link import targets; targets()\""
|
||||
|
@ -69,11 +65,10 @@ class TestLink(unittest.TestCase):
|
|||
close_rns()
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_00_valid_announce(self):
|
||||
def test_0_valid_announce(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
|
||||
print("Testing valid announce...")
|
||||
fid = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[3][0]))
|
||||
dst = RNS.Destination(fid, RNS.Destination.IN, RNS.Destination.SINGLE, "test", "announce")
|
||||
ap = dst.announce(send=False)
|
||||
|
@ -81,11 +76,10 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(RNS.Identity.validate_announce(ap), True)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_01_invalid_announce(self):
|
||||
def test_1_invalid_announce(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
|
||||
print("Testing invalid announce...")
|
||||
fid = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[4][0]))
|
||||
dst = RNS.Destination(fid, RNS.Destination.IN, RNS.Destination.SINGLE, "test", "announce")
|
||||
ap = dst.announce(send=False)
|
||||
|
@ -98,7 +92,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(RNS.Identity.validate_announce(ap), False)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_02_establish(self):
|
||||
def test_2_establish(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
|
||||
|
@ -112,42 +106,18 @@ class TestLink(unittest.TestCase):
|
|||
|
||||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
print("Testing default mode link establishment...")
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
print("Testing AES_128_CBC mode link establishment...")
|
||||
l2 = RNS.Link(dest, mode=RNS.Link.MODE_AES128_CBC)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
self.assertEqual(l2.status, RNS.Link.ACTIVE)
|
||||
self.assertEqual(l2.mode, RNS.Link.MODE_AES128_CBC)
|
||||
self.assertEqual(len(l2.derived_key), 32)
|
||||
|
||||
l2.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
self.assertEqual(l2.status, RNS.Link.CLOSED)
|
||||
|
||||
print("Testing AES_256_CBC mode link establishment...")
|
||||
l3 = RNS.Link(dest, mode=RNS.Link.MODE_AES256_CBC)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
self.assertEqual(l3.status, RNS.Link.ACTIVE)
|
||||
self.assertEqual(l3.mode, RNS.Link.MODE_AES256_CBC)
|
||||
self.assertEqual(len(l3.derived_key), 64)
|
||||
|
||||
l3.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
self.assertEqual(l3.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_03a_packets(self):
|
||||
def test_3_packets(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Testing default mode link packets...")
|
||||
|
||||
# TODO: Load this from public bytes only
|
||||
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
|
@ -161,7 +131,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
b = 0
|
||||
|
@ -195,7 +165,6 @@ class TestLink(unittest.TestCase):
|
|||
if not r.status == RNS.PacketReceipt.DELIVERED:
|
||||
all_ok = False
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
pduration = time.time()-pstart
|
||||
|
||||
|
@ -213,84 +182,11 @@ class TestLink(unittest.TestCase):
|
|||
print("Single packet and proof round-trip throughput is "+self.size_str(b/pduration, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_03b_packets(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Testing AES_256_CBC mode link packets...")
|
||||
|
||||
# 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]))
|
||||
|
||||
RNS.Transport.request_path(bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
time.sleep(0.2)
|
||||
|
||||
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, mode=RNS.Link.MODE_AES256_CBC)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
b = 0
|
||||
pr_t = 0
|
||||
receipts = []
|
||||
if RNS.Cryptography.backend() == "internal" or RNS.Reticulum.MTU > 500:
|
||||
num_packets = 50
|
||||
else:
|
||||
num_packets = 500
|
||||
|
||||
packet_size = RNS.Link.MDU
|
||||
pstart = time.time()
|
||||
print("Sending "+str(num_packets)+" link packets of "+str(packet_size)+" bytes...")
|
||||
for i in range(0, num_packets):
|
||||
time.sleep(0.003)
|
||||
b += packet_size
|
||||
data = os.urandom(packet_size)
|
||||
start = time.time()
|
||||
p = RNS.Packet(l1, data)
|
||||
receipts.append(p.send())
|
||||
pr_t += time.time() - start
|
||||
|
||||
print("Sent "+self.size_str(b)+", "+self.size_str(b/pr_t, "b")+"ps")
|
||||
print("Checking receipts...", end=" ")
|
||||
|
||||
all_ok = False
|
||||
receipt_timeout = time.time() + 35
|
||||
while not all_ok and time.time() < receipt_timeout:
|
||||
for r in receipts:
|
||||
all_ok = True
|
||||
if not r.status == RNS.PacketReceipt.DELIVERED:
|
||||
all_ok = False
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
pduration = time.time()-pstart
|
||||
|
||||
n_failed = 0
|
||||
for r in receipts:
|
||||
if not r.status == RNS.PacketReceipt.DELIVERED:
|
||||
n_failed += 1
|
||||
|
||||
if n_failed > 0:
|
||||
ns = "s" if n_failed != 1 else ""
|
||||
print("Failed to receive proof for "+str(n_failed)+" packet"+ns)
|
||||
|
||||
self.assertEqual(all_ok, True)
|
||||
print("OK!")
|
||||
print("Single packet and proof round-trip throughput is "+self.size_str(b/pduration, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_04_micro_resource(self):
|
||||
def test_4_micro_resource(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Micro resource test")
|
||||
|
@ -307,7 +203,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
resource_timeout = 120
|
||||
|
@ -323,38 +219,14 @@ class TestLink(unittest.TestCase):
|
|||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
|
||||
# Test sending resource with metadata
|
||||
data = os.urandom(resource_size)
|
||||
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(32)}
|
||||
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
|
||||
resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
|
||||
start = time.time()
|
||||
|
||||
# This is a hack, don't do it. Use the callbacks instead.
|
||||
while resource.status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.01)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
|
||||
# Test sending resource with invalid size metadata
|
||||
data = os.urandom(resource_size)
|
||||
metadata = os.urandom(RNS.Resource.METADATA_MAX_SIZE+1)
|
||||
print("Sending "+self.size_str(resource_size)+" resource with invalid size metadata...")
|
||||
exception_raised = False
|
||||
try: resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
|
||||
except SystemError: exception_raised = True
|
||||
self.assertEqual(exception_raised, True)
|
||||
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_05_mini_resource(self):
|
||||
def test_5_mini_resource(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Mini resource test")
|
||||
|
@ -371,13 +243,11 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
resource_timeout = 120
|
||||
resource_size = 256*1000
|
||||
|
||||
# Test sending resource without metadata
|
||||
data = os.urandom(resource_size)
|
||||
print("Sending "+self.size_str(resource_size)+" resource...")
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout)
|
||||
|
@ -389,29 +259,14 @@ class TestLink(unittest.TestCase):
|
|||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
|
||||
# Test sending resource with metadata
|
||||
data = os.urandom(resource_size)
|
||||
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
|
||||
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
|
||||
resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
|
||||
start = time.time()
|
||||
|
||||
# This is a hack, don't do it. Use the callbacks instead.
|
||||
while resource.status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.01)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_06_small_resource(self):
|
||||
def test_6_small_resource(self):
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Small resource test")
|
||||
|
@ -427,7 +282,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
resource_timeout = 120
|
||||
|
@ -443,30 +298,15 @@ class TestLink(unittest.TestCase):
|
|||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
|
||||
# Test sending resource with metadata
|
||||
data = os.urandom(resource_size)
|
||||
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
|
||||
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
|
||||
resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
|
||||
start = time.time()
|
||||
|
||||
# This is a hack, don't do it. Use the callbacks instead.
|
||||
while resource.status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.01)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_07_medium_resource(self):
|
||||
def test_7_medium_resource(self):
|
||||
if RNS.Cryptography.backend() == "internal":
|
||||
print("Skipping medium resource test...")
|
||||
return
|
||||
|
@ -486,41 +326,26 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
resource_timeout = 120
|
||||
resource_size = 5*1000*1000
|
||||
data = os.urandom(resource_size)
|
||||
print("Sending "+self.size_str(resource_size)+" resource...")
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, auto_compress=False)
|
||||
start = time.time()
|
||||
|
||||
TestLink.large_resource_status = resource.status
|
||||
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.001)
|
||||
# This is a hack, don't do it. Use the callbacks instead.
|
||||
while resource.status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.01)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
|
||||
# Test sending resource with metadata
|
||||
data = os.urandom(resource_size)
|
||||
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
|
||||
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
|
||||
start = time.time()
|
||||
|
||||
TestLink.large_resource_status = resource.status
|
||||
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.001)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
large_resource_status = None
|
||||
|
@ -528,7 +353,7 @@ class TestLink(unittest.TestCase):
|
|||
TestLink.large_resource_status = resource.status
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_09_large_resource(self):
|
||||
def test_9_large_resource(self):
|
||||
if RNS.Cryptography.backend() == "internal":
|
||||
print("Skipping large resource test...")
|
||||
return
|
||||
|
@ -548,7 +373,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
resource_timeout = 120
|
||||
|
@ -558,33 +383,19 @@ class TestLink(unittest.TestCase):
|
|||
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
|
||||
start = time.time()
|
||||
|
||||
TestLink.large_resource_status = resource.status
|
||||
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.001)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(TestLink.large_resource_status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
|
||||
# Test sending resource with metadata
|
||||
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
|
||||
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
|
||||
start = time.time()
|
||||
|
||||
TestLink.large_resource_status = resource.status
|
||||
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
|
||||
time.sleep(0.01)
|
||||
|
||||
t = time.time() - start
|
||||
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
|
||||
print("Resource with metadata completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
|
||||
self.assertEqual(TestLink.large_resource_status, RNS.Resource.COMPLETE)
|
||||
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
#@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_10_channel_round_trip(self):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
|
@ -619,7 +430,7 @@ class TestLink(unittest.TestCase):
|
|||
channel.add_message_handler(handle_message)
|
||||
channel.send(test_message)
|
||||
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(1, len(received))
|
||||
|
||||
|
@ -632,11 +443,11 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(0, len(l1._channel._rx_ring))
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
self.assertEqual(0, len(l1._channel._rx_ring))
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
|
||||
def test_11_buffer_round_trip(self):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
|
@ -655,7 +466,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(1)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
buffer = None
|
||||
|
||||
|
@ -670,7 +481,7 @@ class TestLink(unittest.TestCase):
|
|||
buffer.write("Hi there".encode("utf-8"))
|
||||
buffer.flush()
|
||||
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(1 , len(received))
|
||||
|
||||
|
@ -679,10 +490,10 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual("Hi there back at you", rx_message)
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None and os.getenv('RUN_SLOW_TESTS') == None, "Skipping")
|
||||
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None and os.getenv('RUN_SLOW_TESTS') == None, "Skipping")
|
||||
def test_12_buffer_round_trip_big(self, local_bitrate = None):
|
||||
global c_rns, buffer_read_target
|
||||
init_rns(self)
|
||||
|
@ -784,7 +595,7 @@ class TestLink(unittest.TestCase):
|
|||
self.assertEqual(expected_rx_message, rx_message)
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(LINK_UP_WAIT)
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
finally:
|
||||
local_interface.bitrate = original_bitrate
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue