v1.0.0: Boundary mode with bidirectional LoRa↔TCP transport

Vendor microReticulum library with boundary mode transport fixes:
- Two-whitelist system gates backbone traffic (local addresses +
  mentioned addresses from local devices)
- Allow control_hashes and local destinations through boundary filter
  (fixes backbone→LoRa path discovery)
- Fix get_cached_packet() to call unpack() instead of update_hash()
  (fixes empty destination_hash in path responses)
- LRPROOF Identity::recall null guard
- remaining_hops HEADER_1/BROADCAST fix for final-hop delivery
- PROOF packets excluded from boundary wrapping
- Iterator invalidation fix in transport table cleanup
- is_backbone flag replaces string matching for interface identification

Firmware changes:
- Set is_backbone(true) on backbone TCP interface
- Rename default TcpInterface name to BackboneInterface
- Update comments for dual-use TcpInterface (backbone + local AP)
- Use vendored lib/microReticulum instead of PlatformIO registry
This commit is contained in:
James L
2026-02-23 18:08:29 -05:00
parent e741d1cd64
commit 5ed70dcca9
66 changed files with 18264 additions and 35 deletions

201
lib/microReticulum/LICENSE Executable file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

31
lib/microReticulum/library.json Executable file
View File

@@ -0,0 +1,31 @@
{
"name": "microReticulum",
"version": "0.2.4",
"description": "Port of Reticulum Network Stack to C++ specifically but not exclusively targeting 32-bit and better MCUs",
"keywords": "reticulum, rns, mesh, embedded, mcu, esp32, nrf52, native",
"repository":
{
"type": "git",
"url": "https://github.com/attermann/microReticulum.git"
},
"authors":
[
{
"name": "Chad Attermann",
"email": "attermann@gmail.com",
"maintainer": true
}
],
"license": "Apache-2.0",
"homepage": "https://reticulum.network/",
"dependencies": {
"ArduinoJson": "~7.4.2",
"MsgPack": "~0.4.2"
},
"frameworks": "*",
"platforms": "*",
"export": {},
"exclude": [
".github"
]
}

View File

@@ -0,0 +1,10 @@
name=microReticulum
version=0.2.4
author=Chad Attermann <attermann@gmail.com>
maintainer=Chad Attermann <attermann@gmail.com>
sentence=Port of Reticulum Network Stack to C++
paragraph=Specifically but not exclusively targeting 32-bit and better MCUs
category=Communication
url=https://github.com/attermann/microReticulum.git
architectures=*
depends=ArduinoJson,MsgPack

201
lib/microReticulum/src/Bytes.cpp Executable file
View File

@@ -0,0 +1,201 @@
#include "Bytes.h"
using namespace RNS;
// Creates new shared data for instance
// - If capacity is specified (>0) then create empty shared data with initial reserved capacity
// - If capacity is not specified (<=0) then create empty shared data with no initial capacity
void Bytes::newData(size_t capacity /*= 0*/) {
//MEMF("Bytes is creating own data with capacity %u", capacity);
//MEM("newData: Creating new data...");
Data* data = new Data();
if (data == nullptr) {
ERROR("Bytes failed to allocate empty data buffer");
throw std::runtime_error("Failed to allocate empty data buffer");
}
//MEM("newData: Created new data");
if (capacity > 0) {
//MEMF("newData: Reserving data capacity of %u...", capacity);
data->reserve(capacity);
//MEM("newData: Reserved data capacity");
}
//MEM("newData: Assigning data to shared data pointer...");
_data = SharedData(data);
//MEM("newData: Assigned data to shared data pointer");
_exclusive = true;
}
// Ensures that instance has exclusive shared data
// - If instance has no shared data then create new shared data
// - If instance does not have exclusive on shared data that is not empty then make a copy of shared data (if requests) and reserve capacity (if requested)
// - If instance does not have exclusive on shared data that is empty then create new shared data
// - If instance already has exclusive on shared data then do nothing except reserve capacity (if requested)
void Bytes::exclusiveData(bool copy /*= true*/, size_t capacity /*= 0*/) {
if (!_data) {
newData(capacity);
}
else if (!_exclusive) {
if (copy && !_data->empty()) {
//TRACE("Bytes is creating a writable copy of its shared data");
//Data* data = new Data(*_data.get());
//MEM("exclusiveData: Creating new data...");
Data* data = new Data();
if (data == nullptr) {
ERROR("Bytes failed to duplicate data buffer");
throw std::runtime_error("Failed to duplicate data buffer");
}
//MEM("exclusiveData: Created new data");
if (capacity > 0) {
//MEMF("exclusiveData: Reserving data capacity of %u...", capacity);
// if requested capacity < existing size then reserve capacity for existing size instead
data->reserve((capacity > _data->size()) ? capacity : _data->size());
//MEM("exclusiveData: Reserved data capacity");
}
else {
data->reserve(_data->size());
}
//MEM("exclusiveData: Copying existing data...");
data->insert(data->begin(), _data->begin(), _data->end());
//MEM("exclusiveData: Copied existing data");
//MEM("exclusiveData: Assigning data to shared data pointer...");
_data = SharedData(data);
//MEM("exclusiveData: Assigned data to shared data pointer");
_exclusive = true;
}
else {
//MEM("Bytes is creating its own data because shared is empty");
//data = new Data();
//if (data == nullptr) {
// ERROR("Bytes failed to allocate empty data buffer");
// throw std::runtime_error("Failed to allocate empty data buffer");
//}
//_data = SharedData(data);
//_exclusive = true;
//MEM("exclusiveData: Creating new empty data...");
newData(capacity);
//MEM("exclusiveData: Created new empty data");
}
}
else if (capacity > 0 && capacity > size()) {
reserve(capacity);
}
}
int Bytes::compare(const Bytes& bytes) const {
if (_data == bytes._data) {
return 0;
}
else if (!_data) {
return -1;
}
else if (!bytes._data) {
return 1;
}
else if (*_data < *(bytes._data)) {
return -1;
}
else if (*_data > *(bytes._data)) {
return 1;
}
else {
return 0;
}
}
int Bytes::compare(const uint8_t* buf, size_t size) const {
if (!_data && size == 0) {
return 0;
}
else if (!_data) {
return -1;
}
int cmp = memcmp(_data->data(), buf, (_data->size() < size) ? _data->size() : size);
if (cmp == 0 && _data->size() < size) {
return -1;
}
else if (cmp == 0 && _data->size() > size) {
return 1;
}
return cmp;
}
void Bytes::assignHex(const uint8_t* hex, size_t hex_size) {
// if assignment is empty then clear data and don't bother creating new
if (hex == nullptr || hex_size <= 0) {
_data = nullptr;
_exclusive = true;
return;
}
exclusiveData(false, hex_size / 2);
// need to clear data since we're appending below
_data->clear();
for (size_t i = 0; i < hex_size; i += 2) {
uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
_data->push_back(byte);
}
}
void Bytes::appendHex(const uint8_t* hex, size_t hex_size) {
// if append is empty then do nothing
if (hex == nullptr || hex_size <= 0) {
return;
}
exclusiveData(true, size() + (hex_size / 2));
for (size_t i = 0; i < hex_size; i += 2) {
uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
_data->push_back(byte);
}
}
const char hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
const char hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
std::string RNS::hexFromByte(uint8_t byte, bool upper /*= true*/) {
std::string hex;
if (upper) {
hex += hex_upper_chars[ (byte& 0xF0) >> 4];
hex += hex_upper_chars[ (byte& 0x0F) >> 0];
}
else {
hex += hex_lower_chars[ (byte& 0xF0) >> 4];
hex += hex_lower_chars[ (byte& 0x0F) >> 0];
}
return hex;
}
std::string Bytes::toHex(bool upper /*= false*/) const {
if (!_data) {
return "";
}
std::string hex;
for (uint8_t byte : *_data) {
if (upper) {
hex += hex_upper_chars[ (byte& 0xF0) >> 4];
hex += hex_upper_chars[ (byte& 0x0F) >> 0];
}
else {
hex += hex_lower_chars[ (byte& 0xF0) >> 4];
hex += hex_lower_chars[ (byte& 0x0F) >> 0];
}
}
return hex;
}
// mid
Bytes Bytes::mid(size_t beginpos, size_t len) const {
if (!_data || beginpos >= size()) {
return NONE;
}
if ((beginpos + len) >= size()) {
len = (size() - beginpos);
}
return {data() + beginpos, len};
}
// to end
Bytes Bytes::mid(size_t beginpos) const {
if (!_data || beginpos >= size()) {
return NONE;
}
return {data() + beginpos, size() - beginpos};
}

447
lib/microReticulum/src/Bytes.h Executable file
View File

@@ -0,0 +1,447 @@
#pragma once
#include "Log.h"
#include <ArduinoJson.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include <memory>
// Finds the start of the first occurrence of the substring needle of length needlelen in the memory area haystack of length haystacklen.
inline void* memmem(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len) {
const unsigned char* h = (const unsigned char*)haystack;
const unsigned char* n = (const unsigned char*)needle;
if (needle_len == 0)
return (void*)h;
if (haystack_len < needle_len)
return nullptr;
for (size_t i = 0; i <= haystack_len - needle_len; i++) {
if (h[i] == n[0] && memcmp(&h[i], n, needle_len) == 0) {
return (void*)&h[i];
}
}
return nullptr;
}
namespace RNS {
#define COW
class Bytes {
private:
//typedef std::vector<uint8_t> Data;
using Data = std::vector<uint8_t>;
//typedef std::shared_ptr<Data> SharedData;
using SharedData = std::shared_ptr<Data>;
public:
enum NoneConstructor {
NONE
};
public:
Bytes() {
MEMF("Bytes object created from default, this: %lu, data: %lu", this, _data.get());
}
Bytes(const NoneConstructor none) {
MEMF("Bytes object created from NONE, this: %lu, data: %lu", this, _data.get());
}
Bytes(const Bytes& bytes) {
//MEM("Bytes is using shared data");
assign(bytes);
MEMF("Bytes object copy created from bytes \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
// Construct from std::vector<uint8_t>
Bytes(const Data& data) {
MEM("Creating from data-copy...");
assign(data);
MEMF("Bytes object created from data-copy \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
// Construct from rvalue std::vector<uint8_t> (move)
Bytes(Data&& rdata) {
MEM("Creating from data-move...");
assign(std::move(rdata));
MEMF("Bytes object created from data-move \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
Bytes(const uint8_t* chunk, size_t size) {
assign(chunk, size);
MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
Bytes(const void* chunk, size_t size) {
assign(chunk, size);
MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
Bytes(const char* string) {
assign(string);
MEMF("Bytes object created from string \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
Bytes(const std::string& string) {
assign(string);
MEMF("Bytes object created from std::string \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
Bytes(size_t capacity) {
newData(capacity);
MEMF("Bytes object created with capacity %u, this: %lu, data: %lu", capacity, this, _data.get());
}
virtual ~Bytes() {
MEMF("Bytes object destroyed \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
inline const Bytes& operator = (const Bytes& bytes) {
assign(bytes);
return *this;
}
inline const Bytes& operator += (const Bytes& bytes) {
append(bytes);
return *this;
}
// CBA TODO Resolve ambiguity in JsonVariantConst assignments before enabling the following
/*
// Assignment from std::vector<uint8_t>
inline const Bytes& operator = (const Data& data) {
assign(data);
return *this;
}
// Move assignment from std::vector<uint8_t>
inline const Bytes& operator = (Data&& rdata) {
assign(std::move(rdata));
return *this;
}
*/
inline const Bytes& operator += (const Data& data) {
append(data);
return *this;
}
inline Bytes operator + (const Bytes& bytes) const {
Bytes newbytes(*this);
newbytes.append(bytes);
return newbytes;
}
inline bool operator == (const Bytes& bytes) const {
return compare(bytes) == 0;
}
inline bool operator != (const Bytes& bytes) const {
return compare(bytes) != 0;
}
inline bool operator < (const Bytes& bytes) const {
return compare(bytes) < 0;
}
inline bool operator > (const Bytes& bytes) const {
return compare(bytes) > 0;
}
inline uint8_t& operator[](size_t index) {
if (!_data || index >= _data->size()) {
throw std::out_of_range("Index out of bounds");
}
return (*_data)[index];
}
inline const uint8_t& operator[](size_t index) const {
if (!_data || index >= _data->size()) {
throw std::out_of_range("Index out of bounds");
}
return (*_data)[index];
}
inline operator bool() const {
return (_data && !_data->empty());
}
inline operator const Data() const {
if (!_data)
return Data();
return *_data.get();
}
// CBA NOTE: Following cast operators can cause issues with ambiguity from other libraries
/*
inline operator const uint8_t*() const {
if (!_data)
return nullptr;
return _data->data();
}
inline operator const void*() const {
if (!_data)
return nullptr;
return _data->data();
}
*/
private:
inline SharedData shareData() const {
//MEM("Bytes is sharing its own data");
_exclusive = false;
return _data;
}
void newData(size_t capacity = 0);
void exclusiveData(bool copy = true, size_t capacity = 0);
public:
inline void clear() {
_data = nullptr;
_exclusive = true;
}
inline void assign(const Bytes& bytes) {
#ifdef COW
_data = bytes.shareData();
_exclusive = false;
#else
// if assignment is empty then clear data and don't bother creating new
if (bytes.size() <= 0) {
_data = nullptr;
_exclusive = true;
return;
}
exclusiveData(false, bytes._data->size());
//_data->insert(_data->begin(), bytes._data->begin(), bytes._data->end());
//_data->assign(bytes._data->begin(), bytes._data->end());
*_data = bytes._data;
#endif
}
inline void assign(const Data& data) {
// if assignment is empty then clear data and don't bother creating new
if (data.size() <= 0) {
_data = nullptr;
_exclusive = true;
return;
}
exclusiveData(false, data.size());
//_data->assign(data.begin(), data.end());
*_data = data;
}
inline void assign(Data&& rdata) {
// if assignment is empty then clear data and don't bother creating new
if (rdata.size() <= 0) {
_data = nullptr;
_exclusive = true;
return;
}
exclusiveData(false);
*_data = std::move(rdata);
}
inline void assign(const uint8_t* chunk, size_t chunk_size) {
// if assignment is empty then clear data and don't bother creating new
if (chunk == nullptr || chunk_size <= 0) {
_data = nullptr;
_exclusive = true;
return;
}
exclusiveData(false, chunk_size);
//_data->insert(_data->begin(), chunk, chunk + chunk_size);
_data->assign(chunk, chunk + chunk_size);
}
inline void assign(const void* chunk, size_t chunk_size) {
assign((uint8_t*)chunk, chunk_size);
}
inline void assign(const char* string) {
// if assignment is empty then clear data and don't bother creating new
if (string == nullptr || string[0] == 0) {
_data = nullptr;
_exclusive = true;
return;
}
size_t string_size = strlen(string);
exclusiveData(false, string_size);
//_data->insert(_data->begin(), (uint8_t* )string, (uint8_t* )string + string_size);
_data->assign((uint8_t* )string, (uint8_t* )string + string_size);
}
//inline void assign(const std::string& string) { assign(string.c_str()); }
inline void assign(const std::string& string) { assign((uint8_t*)string.c_str(), string.length()); }
void assignHex(const uint8_t* hex, size_t hex_size);
inline void assignHex(const char* hex) { assignHex((uint8_t*)hex, strlen(hex)); }
inline void append(const Bytes& bytes) {
// if append is empty then do nothing
if (bytes.size() <= 0) {
return;
}
exclusiveData(true, size() + bytes.size());
_data->insert(_data->end(), bytes._data->begin(), bytes._data->end());
}
inline void append(const Data& data) {
// if append is empty then do nothing
if (data.size() <= 0) {
return;
}
exclusiveData(true, size() + data.size());
_data->insert(_data->end(), data.begin(), data.end());
}
inline void append(const uint8_t* chunk, size_t chunk_size) {
// if append is empty then do nothing
if (chunk == nullptr || chunk_size <= 0) {
return;
}
exclusiveData(true, size() + chunk_size);
_data->insert(_data->end(), chunk, chunk + chunk_size);
}
inline void append(const void* chunk, size_t chunk_size) {
append((uint8_t*)chunk, chunk_size);
}
inline void append(const char* string) {
// if append is empty then do nothing
if (string == nullptr || string[0] == 0) {
return;
}
size_t string_size = strlen(string);
exclusiveData(true, size() + string_size);
_data->insert(_data->end(), (uint8_t* )string, (uint8_t* )string + string_size);
}
inline void append(uint8_t byte) {
exclusiveData(true, size() + 1);
_data->push_back(byte);
}
//inline void append(const std::string& string) { append(string.c_str()); }
inline void append(const std::string& string) { append((uint8_t*)string.c_str(), string.length()); }
void appendHex(const uint8_t* hex, size_t hex_size);
inline void appendHex(const char* hex) { appendHex((uint8_t*)hex, strlen(hex)); }
inline uint8_t* writable(size_t size) {
if (size > 0) {
// create exclusive data with reserved capacity
exclusiveData(false, size);
// actually expand data to requested size
resize(size);
return _data->data();
}
else if (_data) {
size_t current_size = _data->size();
// create exclusive data with reserved capacity
exclusiveData(false, current_size);
// actually expand data to current size
resize(current_size);
return _data->data();
}
return nullptr;
}
inline void resize(size_t newsize) {
// if size is unchanged then do nothing
if (newsize == size()) {
return;
}
// CBA TODO Determine whether or not to reserve capacity here since when size is shrunk the call to
// exclusive data may copy all data firt which will effectively grow the data again before being
// shrunk below (inefficient).
exclusiveData(true);
_data->resize(newsize);
}
public:
int compare(const Bytes& bytes) const;
int compare(const uint8_t* buf, size_t size) const;
inline int compare(const char* str) const { return compare((const uint8_t*)str, strlen(str)); }
inline size_t size() const { if (!_data) return 0; return _data->size(); }
inline bool empty() const { if (!_data) return true; return _data->empty(); }
inline size_t capacity() const { if (!_data) return 0; return _data->capacity(); }
inline void reserve(size_t capacity) const { if (!_data) return; _data->reserve(capacity); }
inline const uint8_t* data() const { if (!_data) return nullptr; return _data->data(); }
inline const Data collection() const { if (!_data) return Data(); return *_data.get(); }
inline std::string toString() const { if (!_data) return ""; return {(const char*)data(), size()}; }
std::string toHex(bool upper = false) const;
Bytes mid(size_t beginpos, size_t len) const;
Bytes mid(size_t beginpos) const;
inline Bytes left(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data(), len}; }
inline Bytes right(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data() + (size() - len), len}; }
inline int find(int pos, const char* str) {
if (!_data || _data->data() == nullptr || (size_t)pos >= _data->size()) {
return -1;
}
//const char* ptr = strnstr((const char*)(_data->data() + pos), str, (_data->size() - pos));
void* ptr = memmem((const void*)(_data->data() + pos), (_data->size() - pos), (const void*)str, strlen(str));
if (ptr == nullptr) {
return -1;
}
return (int)((uint8_t*)ptr - _data->data());
}
inline int find(const char* str) { return find(0, str); }
// Python array indexing
// [8:16]
// pos 8 to pos 16
// mid(8, 8)
// [:16]
// start to pos 16 (same as first 16)
// left(16)
// [16:]
// pos 16 to end
// mid(16)
// [-16:]
// last 16
// right(16)
// [:-16]
// all except the last 16
// left(size()-16)
// mid(0, size()-16)
// [-1]
// last element
// [-2]
// second to last element
private:
SharedData _data;
mutable bool _exclusive = true;
};
// following array function doesn't work without size since it's passed as a pointer to the array so sizeof() is of the pointer
//inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); }
//inline Bytes bytesFromChunk(const uint8_t* ptr, size_t len) { return Bytes(ptr, len); }
inline Bytes bytesFromChunk(const uint8_t* ptr, size_t len) { return {ptr, len}; }
//inline Bytes bytesFromString(const char* str) { return Bytes((uint8_t*)str, strlen(str)); }
inline Bytes bytesFromString(const char* str) { return {(uint8_t*)str, strlen(str)}; }
//z inline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; }
inline std::string stringFromBytes(const Bytes& bytes) { return bytes.toString(); }
inline std::string hexFromBytes(const Bytes& bytes) { return bytes.toHex(); }
std::string hexFromByte(uint8_t byte, bool upper = true);
}
inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, const RNS::Bytes& rhbytes) {
//MEM("Appending right-hand Bytes to left-hand Bytes");
lhbytes.append(rhbytes);
//MEM("Returning left-hand Bytes");
return lhbytes;
}
inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, uint8_t rhbyte) {
//MEM("Appending right-hand byte to left-hand Bytes");
lhbytes.append(rhbyte);
//MEM("Returning left-hand Bytes");
return lhbytes;
}
inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, const char* rhstr) {
//MEM("Appending right-hand str to left-hand Bytes");
lhbytes.append(rhstr);
//MEM("Returning left-hand Bytes");
return lhbytes;
}
namespace ArduinoJson {
// Serialize
inline bool convertToJson(const RNS::Bytes& src, JsonVariant dst) {
return dst.set(src.toHex());
}
// Deserialize
inline void convertFromJson(JsonVariantConst src, RNS::Bytes& dst) {
dst.assignHex(src.as<const char*>());
}
inline bool canConvertFromJson(JsonVariantConst src, const RNS::Bytes&) {
return src.is<const char*>();
}
}

View File

@@ -0,0 +1,12 @@
#include "Channel.h"
#include "Reticulum.h"
#include "Transport.h"
#include "Packet.h"
#include "Log.h"
#include <algorithm>
using namespace RNS;
using namespace RNS::Type::Channel;
using namespace RNS::Utilities;

View File

@@ -0,0 +1,57 @@
#pragma once
#include "Destination.h"
#include "Type.h"
#include <memory>
#include <cassert>
namespace RNS {
class Channel {
public:
Channel(Type::NoneConstructor none) {
MEM("Channel NONE object created");
}
Channel(const Channel& resource) : _object(resource._object) {
MEM("Channel object copy created");
}
virtual ~Channel(){
MEM("Channel object destroyed");
}
Channel& operator = (const Channel& resource) {
_object = resource._object;
return *this;
}
operator bool() const {
return _object.get() != nullptr;
}
bool operator < (const Channel& resource) const {
return _object.get() < resource._object.get();
//return _object->_hash < resource._object->_hash;
}
public:
void _shutdown() {}
private:
class Object {
public:
Object() { MEM("Channel::Data object created, this: " + std::to_string((uintptr_t)this)); }
virtual ~Object() { MEM("Channel::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
private:
friend class Channel;
};
std::shared_ptr<Object> _object;
};
class ChannelAdvertisement {
};
}

View File

@@ -0,0 +1,89 @@
#pragma once
#include "CBC.h"
#include "../Bytes.h"
#include <AES.h>
namespace RNS { namespace Cryptography {
class AES_128_CBC {
public:
static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) {
CBC<AES128> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
Bytes ciphertext;
cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size());
return ciphertext;
}
static inline const Bytes decrypt(const Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
CBC<AES128> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
Bytes plaintext;
cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size());
return plaintext;
}
// EXPERIMENTAL - overwrites passed buffer
static inline void inplace_encrypt(Bytes& plaintext, const Bytes& key, const Bytes& iv) {
CBC<AES128> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size());
}
// EXPERIMENTAL - overwrites passed buffer
static inline void inplace_decrypt(Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
CBC<AES128> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size());
}
};
class AES_256_CBC {
public:
static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) {
CBC<AES256> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
Bytes ciphertext;
cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size());
return ciphertext;
}
static inline const Bytes decrypt(const Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
CBC<AES256> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
Bytes plaintext;
cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size());
return plaintext;
}
// EXPERIMENTAL - overwrites passed buffer
static inline void inplace_encrypt(Bytes& plaintext, const Bytes& key, const Bytes& iv) {
CBC<AES256> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size());
}
// EXPERIMENTAL - overwrites passed buffer
static inline void inplace_decrypt(Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
CBC<AES256> cbc;
cbc.setKey(key.data(), key.size());
cbc.setIV(iv.data(), iv.size());
cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size());
}
};
} }

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2015 Southern Storm Software, Pty Ltd.
*
* 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.
*/
#include "CBC.h"
#include <Crypto.h>
#include <string.h>
/**
* \class CBCCommon CBC.h <CBC.h>
* \brief Concrete base class to assist with implementing CBC for
* 128-bit block ciphers.
*
* Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
*
* \sa CBC
*/
/**
* \brief Constructs a new cipher in CBC mode.
*
* This constructor should be followed by a call to setBlockCipher().
*/
CBCCommon::CBCCommon()
: blockCipher(0)
, posn(16)
{
}
/**
* \brief Destroys this cipher object after clearing sensitive information.
*/
CBCCommon::~CBCCommon()
{
clean(iv);
clean(temp);
}
size_t CBCCommon::keySize() const
{
return blockCipher->keySize();
}
size_t CBCCommon::ivSize() const
{
return 16;
}
bool CBCCommon::setKey(const uint8_t* key, size_t len)
{
// Verify the cipher's block size, just in case.
if (blockCipher->blockSize() != 16)
return false;
// Set the key on the underlying block cipher.
return blockCipher->setKey(key, len);
}
bool CBCCommon::setIV(const uint8_t* iv, size_t len)
{
if (len != 16)
return false;
memcpy(this->iv, iv, 16);
posn = 16;
return true;
}
void CBCCommon::encrypt(uint8_t* output, const uint8_t* input, size_t len)
{
uint8_t posn;
while (len >= 16) {
for (posn = 0; posn < 16; ++posn)
iv[posn] ^= *input++;
blockCipher->encryptBlock(iv, iv);
for (posn = 0; posn < 16; ++posn)
*output++ = iv[posn];
len -= 16;
}
}
void CBCCommon::decrypt(uint8_t* output, const uint8_t* input, size_t len)
{
uint8_t posn;
while (len >= 16) {
blockCipher->decryptBlock(temp, input);
for (posn = 0; posn < 16; ++posn) {
uint8_t in = *input++;
*output++ = temp[posn] ^ iv[posn];
iv[posn] = in;
}
len -= 16;
}
}
void CBCCommon::clear()
{
blockCipher->clear();
clean(iv);
clean(temp);
posn = 16;
}
/**
* \fn void CBCCommon::setBlockCipher(BlockCipher *cipher)
* \brief Sets the block cipher to use for this CBC object.
*
* \param cipher The block cipher to use to implement CBC mode,
* which must have a block size of 16 bytes (128 bits).
*/
/**
* \class CBC CBC.h <CBC.h>
* \brief Implementation of the Cipher Block Chaining (CBC) mode for
* 128-bit block ciphers.
*
* The template parameter T must be a concrete subclass of BlockCipher
* indicating the specific block cipher to use. T must have a block size
* of 16 bytes (128 bits).
*
* For example, the following creates a CBC object using AES192 as the
* underlying cipher:
*
* \code
* CBC<AES192> cbc;
* cbc.setKey(key, 24);
* cbc.setIV(iv, 16);
* cbc.encrypt(output, input, len);
* \endcode
*
* Decryption is similar:
*
* \code
* CBC<AES192> cbc;
* cbc.setKey(key, 24);
* cbc.setIV(iv, 16);
* cbc.decrypt(output, input, len);
* \endcode
*
* The size of the ciphertext will always be the same as the size of
* the plaintext. Also, the length of the plaintext/ciphertext must be a
* multiple of 16. Extra bytes are ignored and not encrypted. The caller
* is responsible for padding the underlying data to a multiple of 16
* using an appropriate padding scheme for the application.
*
* Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
*
* \sa CTR, CFB, OFB
*/
/**
* \fn CBC::CBC()
* \brief Constructs a new CBC object for the block cipher T.
*/

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2015 Southern Storm Software, Pty Ltd.
*
* 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.
*/
#ifndef CRYPTO_CBC_h
#define CRYPTO_CBC_h
#include <Cipher.h>
#include <BlockCipher.h>
class CBCCommon : public Cipher
{
public:
virtual ~CBCCommon();
size_t keySize() const;
size_t ivSize() const;
bool setKey(const uint8_t* key, size_t len);
bool setIV(const uint8_t* iv, size_t len);
void encrypt(uint8_t* output, const uint8_t* input, size_t len);
void decrypt(uint8_t* output, const uint8_t* input, size_t len);
void clear();
protected:
CBCCommon();
void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
private:
BlockCipher *blockCipher;
uint8_t iv[16];
uint8_t temp[16];
uint8_t posn;
};
template <typename T>
class CBC : public CBCCommon
{
public:
CBC() { setBlockCipher(&cipher); }
private:
T cipher;
};
#endif

View File

@@ -0,0 +1,3 @@
#include "Ed25519.h"
using namespace RNS::Cryptography;

View File

@@ -0,0 +1,102 @@
#pragma once
#include "Bytes.h"
#include <Ed25519.h>
#include <memory>
/*
Note that the library currently in use for Ed25519 does not support generating keys from a seed.
*/
namespace RNS { namespace Cryptography {
class Ed25519PublicKey {
public:
using Ptr = std::shared_ptr<Ed25519PublicKey>;
public:
Ed25519PublicKey(const Bytes& publicKey) {
_publicKey = publicKey;
}
~Ed25519PublicKey() {}
public:
// creates a new instance with specified seed
static inline Ptr from_public_bytes(const Bytes& publicKey) {
return Ptr(new Ed25519PublicKey(publicKey));
}
inline const Bytes& public_bytes() {
return _publicKey;
}
inline bool verify(const Bytes& signature, const Bytes& message) {
return Ed25519::verify(signature.data(), _publicKey.data(), message.data(), message.size());
}
private:
Bytes _publicKey;
};
class Ed25519PrivateKey {
public:
using Ptr = std::shared_ptr<Ed25519PrivateKey>;
public:
Ed25519PrivateKey(const Bytes& privateKey) {
if (privateKey) {
// use specified private key
_privateKey = privateKey;
}
else {
// create random private key
Ed25519::generatePrivateKey(_privateKey.writable(32));
}
// derive public key from private key
Ed25519::derivePublicKey(_publicKey.writable(32), _privateKey.data());
}
~Ed25519PrivateKey() {}
public:
// creates a new instance with a random seed
static inline Ptr generate() {
// CBA TODO determine why below is confused with (implicit) copy constructor
//return Ptr(new Ed25519PrivateKey({Bytes::NONE}));
return Ptr(new Ed25519PrivateKey(Bytes::NONE));
}
// creates a new instance with specified seed
static inline Ptr from_private_bytes(const Bytes& privateKey) {
return Ptr(new Ed25519PrivateKey(privateKey));
}
inline const Bytes& private_bytes() {
return _privateKey;
}
// creates a new instance of public key for this private key
inline Ed25519PublicKey::Ptr public_key() {
return Ed25519PublicKey::from_public_bytes(_publicKey);
}
inline const Bytes sign(const Bytes& message) {
//z return _sk.sign(message);
Bytes signature;
Ed25519::sign(signature.writable(64), _privateKey.data(), _publicKey.data(), message.data(), message.size());
return signature;
}
private:
Bytes _privateKey;
Bytes _publicKey;
};
} }

View File

@@ -0,0 +1,116 @@
#include "Fernet.h"
#include "HMAC.h"
#include "PKCS7.h"
#include "AES.h"
#include "../Log.h"
#include <stdexcept>
#include <time.h>
using namespace RNS;
using namespace RNS::Cryptography;
Fernet::Fernet(const Bytes& key) {
if (!key) {
throw std::invalid_argument("Fernet key cannot be None");
}
if (key.size() != 32) {
throw std::invalid_argument("Fernet key must be 32 bytes, not " + std::to_string(key.size()));
}
//self._signing_key = key[:16]
_signing_key = key.left(16);
//self._encryption_key = key[16:]
_encryption_key = key.mid(16);
MEM("Fernet object created");
}
Fernet::~Fernet() {
MEM("Fernet object destroyed");
}
bool Fernet::verify_hmac(const Bytes& token) {
if (token.size() <= 32) {
throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes");
}
//received_hmac = token[-32:]
Bytes received_hmac = token.right(32);
DEBUG("Fernet::verify_hmac: received_hmac: " + received_hmac.toHex());
//expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest();
DEBUG("Fernet::verify_hmac: expected_hmac: " + expected_hmac.toHex());
return (received_hmac == expected_hmac);
}
const Bytes Fernet::encrypt(const Bytes& data) {
DEBUG("Fernet::encrypt: plaintext length: " + std::to_string(data.size()));
Bytes iv = random(16);
//double current_time = OS::time();
TRACE("Fernet::encrypt: iv: " + iv.toHex());
TRACE("Fernet::encrypt: plaintext: " + data.toHex());
Bytes ciphertext = AES_128_CBC::encrypt(
PKCS7::pad(data),
_encryption_key,
iv
);
DEBUG("Fernet::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size()));
TRACE("Fernet::encrypt: ciphertext: " + ciphertext.toHex());
Bytes signed_parts = iv + ciphertext;
//return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest();
Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest());
TRACE("Fernet::encrypt: sig: " + sig.toHex());
Bytes token(signed_parts + sig);
DEBUG("Fernet::encrypt: token length: " + std::to_string(token.size()));
return token;
}
const Bytes Fernet::decrypt(const Bytes& token) {
DEBUG("Fernet::decrypt: token length: " + std::to_string(token.size()));
if (token.size() < 48) {
throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes");
}
if (!verify_hmac(token)) {
throw std::invalid_argument("Fernet token HMAC was invalid");
}
//iv = token[:16]
Bytes iv = token.left(16);
TRACE("Fernet::decrypt: iv: " + iv.toHex());
//ciphertext = token[16:-32]
Bytes ciphertext = token.mid(16, token.size()-48);
TRACE("Fernet::decrypt: ciphertext: " + ciphertext.toHex());
try {
Bytes plaintext = PKCS7::unpad(
AES_128_CBC::decrypt(
ciphertext,
_encryption_key,
iv
)
);
DEBUG("Fernet::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size()));
TRACE("Fernet::decrypt: plaintext: " + plaintext.toHex());
DEBUG("Fernet::decrypt: plaintext length: " + std::to_string(plaintext.size()));
return plaintext;
}
catch (std::exception& e) {
WARNING("Could not decrypt Fernet token");
throw std::runtime_error("Could not decrypt Fernet token");
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include "Random.h"
#include "../Bytes.h"
#include <stdint.h>
namespace RNS { namespace Cryptography {
/*
This class provides a slightly modified implementation of the Fernet spec
found at: https://github.com/fernet/spec/blob/master/Spec.md
According to the spec, a Fernet token includes a one byte VERSION and
eight byte TIMESTAMP field at the start of each token. These fields are
not relevant to Reticulum. They are therefore stripped from this
implementation, since they incur overhead and leak initiator metadata.
*/
class Fernet {
public:
using Ptr = std::shared_ptr<Fernet>;
public:
static inline const Bytes generate_key() { return random(32); }
public:
Fernet(const Bytes& key);
~Fernet();
public:
bool verify_hmac(const Bytes& token);
const Bytes encrypt(const Bytes& data);
const Bytes decrypt(const Bytes& token);
private:
Bytes _signing_key;
Bytes _encryption_key;
};
} }

View File

@@ -0,0 +1,28 @@
#include "HKDF.h"
#include <HKDF.h>
#include <SHA256.h>
using namespace RNS;
const Bytes RNS::Cryptography::hkdf(size_t length, const Bytes& derive_from, const Bytes& salt /*= {Bytes::NONE}*/, const Bytes& context /*= {Bytes::NONE}*/) {
if (length <= 0) {
throw std::invalid_argument("Invalid output key length");
}
if (!derive_from) {
throw std::invalid_argument("Cannot derive key from empty input material");
}
HKDF<SHA256> hkdf;
if (salt) {
hkdf.setKey(derive_from.data(), derive_from.size(), salt.data(), salt.size());
}
else {
hkdf.setKey(derive_from.data(), derive_from.size());
}
Bytes derived;
hkdf.extract(derived.writable(length), length);
return derived;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "../Bytes.h"
namespace RNS { namespace Cryptography {
const Bytes hkdf(size_t length, const Bytes& derive_from, const Bytes& salt = {Bytes::NONE}, const Bytes& context = {Bytes::NONE});
} }

View File

@@ -0,0 +1,109 @@
#pragma once
#include "../Bytes.h"
#include <Hash.h>
#include <SHA256.h>
#include <SHA512.h>
#include <stdexcept>
#include <memory>
#include <cassert>
namespace RNS { namespace Cryptography {
class HMAC {
public:
enum Digest {
DIGEST_NONE,
DIGEST_SHA256,
DIGEST_SHA512,
};
using Ptr = std::shared_ptr<HMAC>;
public:
/*
Create a new HMAC object.
key: bytes or buffer, key for the keyed hash object.
msg: bytes or buffer, Initial input for the hash or None.
digest: The underlying hash algorithm to use
*/
HMAC(const Bytes& key, const Bytes& msg = {Bytes::NONE}, Digest digest = DIGEST_SHA256) {
if (digest == DIGEST_NONE) {
throw std::invalid_argument("Cannot derive key from empty input material");
}
switch (digest) {
case DIGEST_SHA256:
_hash = std::unique_ptr<Hash>(new SHA256());
break;
case DIGEST_SHA512:
_hash = std::unique_ptr<Hash>(new SHA512());
break;
default:
throw std::invalid_argument("Unknown ior unsuppored digest");
}
_key = key;
_hash->resetHMAC(key.data(), key.size());
if (msg) {
update(msg);
}
}
/*
Feed data from msg into this hashing object.
*/
void update(const Bytes& msg) {
assert(_hash);
_hash->update(msg.data(), msg.size());
}
/*
Return the hash value of this hashing object.
This returns the hmac value as bytes. The object is
not altered in any way by this function; you can continue
updating the object after calling this function.
*/
Bytes digest() {
assert(_hash);
Bytes result;
_hash->finalizeHMAC(_key.data(), _key.size(), result.writable(_hash->hashSize()), _hash->hashSize());
return result;
}
/*
Create a new hashing object and return it.
key: bytes or buffer, The starting key for the hash.
msg: bytes or buffer, Initial input for the hash, or None.
digest: The underlying hash algorithm to use.
You can now feed arbitrary bytes into the object using its update()
method, and can ask for the hash value at any time by calling its digest()
method.
*/
static inline Ptr generate(const Bytes& key, const Bytes& msg = {Bytes::NONE}, Digest digest = DIGEST_SHA256) {
return Ptr(new HMAC(key, msg, digest));
}
private:
Bytes _key;
std::unique_ptr<Hash> _hash;
};
/*
Fast inline implementation of HMAC.
key: bytes or buffer, The key for the keyed hash object.
msg: bytes or buffer, Input message.
digest: The underlying hash algorithm to use.
*/
inline const Bytes digest(const Bytes& key, const Bytes& msg, HMAC::Digest digest = HMAC::DIGEST_SHA256) {
HMAC hmac(key, msg, digest);
hmac.update(msg);
return hmac.digest();
}
} }

View File

@@ -0,0 +1,36 @@
#include "Hashes.h"
#include "../Bytes.h"
#include <SHA256.h>
#include <SHA512.h>
using namespace RNS;
/*
The SHA primitives are abstracted here to allow platform-
aware hardware acceleration in the future. Currently only
uses Python's internal SHA-256 implementation. All SHA-256
calls in RNS end up here.
*/
const Bytes RNS::Cryptography::sha256(const Bytes& data) {
//TRACE("Cryptography::sha256: data: " + data.toHex() );
SHA256 digest;
digest.reset();
digest.update(data.data(), data.size());
Bytes hash;
digest.finalize(hash.writable(32), 32);
//TRACE("Cryptography::sha256: hash: " + hash.toHex() );
return hash;
}
const Bytes RNS::Cryptography::sha512(const Bytes& data) {
SHA512 digest;
digest.reset();
digest.update(data.data(), data.size());
Bytes hash;
digest.finalize(hash.writable(64), 64);
//TRACE("Cryptography::sha512: hash: " + hash.toHex() );
return hash;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "../Bytes.h"
#include <stdint.h>
namespace RNS { namespace Cryptography {
const Bytes sha256(const Bytes& data);
const Bytes sha512(const Bytes& data);
} }

View File

@@ -0,0 +1,66 @@
#pragma once
#include "../Bytes.h"
//#include "../Log.h"
#include <stdexcept>
namespace RNS { namespace Cryptography {
class PKCS7 {
public:
static const size_t BLOCKSIZE = 16;
static inline const Bytes pad(const Bytes& data, size_t bs = BLOCKSIZE) {
Bytes padded(data);
inplace_pad(padded, bs);
return padded;
}
static inline const Bytes unpad(const Bytes& data, size_t bs = BLOCKSIZE) {
Bytes unpadded(data);
inplace_unpad(unpadded, bs);
return unpadded;
}
// updates passed buffer
static inline void inplace_pad(Bytes& data, size_t bs = BLOCKSIZE) {
size_t len = data.size();
//DEBUG("PKCS7::pad: len: " + std::to_string(len));
size_t padlen = bs - (len % bs);
//DEBUG("PKCS7::pad: pad len: " + std::to_string(padlen));
// create zero-filled byte padding array of size padlen
//p v = bytes([padlen])
//uint8_t pad[padlen] = {0};
uint8_t pad[padlen];
memset(pad, 0, padlen);
// set last byte of padding array to size of padding
pad[padlen-1] = (uint8_t)padlen;
// concatenate data with padding
//p return data+v*padlen
data.append(pad, padlen);
//DEBUG("PKCS7::pad: data size: " + std::to_string(data.size()));
}
// updates passed buffer
static inline void inplace_unpad(Bytes& data, size_t bs = BLOCKSIZE) {
size_t len = data.size();
//DEBUG("PKCS7::unpad: len: " + std::to_string(len));
// read last byte which is pad length
//pad = data[-1]
size_t padlen = (size_t)data.data()[data.size()-1];
//DEBUG("PKCS7::unpad: pad len: " + std::to_string(padlen));
if (padlen > bs) {
throw std::runtime_error("Cannot unpad, invalid padding length of " + std::to_string(padlen) + " bytes");
}
// truncate data to strip padding
//return data[:len-padlen]
data.resize(len - padlen);
//DEBUG("PKCS7::unpad: data size: " + std::to_string(data.size()));
}
};
} }

View File

@@ -0,0 +1,38 @@
#pragma once
#include "../Bytes.h"
#include <RNG.h>
#include <stdint.h>
namespace RNS { namespace Cryptography {
// return vector specified length of random bytes
inline const Bytes random(size_t length) {
Bytes rand;
RNG.rand(rand.writable(length), length);
return rand;
}
// return 32 bit random unigned int
inline uint32_t randomnum() {
Bytes rand;
RNG.rand(rand.writable(4), 4);
uint32_t randnum = uint32_t((unsigned char)(rand.data()[0]) << 24 |
(unsigned char)(rand.data()[0]) << 16 |
(unsigned char)(rand.data()[0]) << 8 |
(unsigned char)(rand.data()[0]));
return randnum;
}
// return 32 bit random unsigned int between 0 and specified value
inline uint32_t randomnum(uint32_t max) {
return randomnum() % max;
}
// return random float value from 0 to 1
inline float random() {
return (float)(randomnum() / (float)0xffffffff);
}
} }

View File

@@ -0,0 +1,159 @@
#include "Token.h"
#include "HMAC.h"
#include "PKCS7.h"
#include "AES.h"
#include "../Log.h"
#include <stdexcept>
#include <time.h>
using namespace RNS;
using namespace RNS::Cryptography;
using namespace RNS::Type::Cryptography::Token;
Token::Token(const Bytes& key, token_mode mode /*= AES*/) {
if (!key) {
throw std::invalid_argument("Token key cannot be None");
}
if (mode == MODE_AES) {
if (key.size() == 32) {
_mode = MODE_AES_128_CBC;
//p self._signing_key = key[:16]
_signing_key = key.left(16);
//p self._encryption_key = key[16:]
_encryption_key = key.mid(16);
}
else if (key.size() == 64) {
_mode = MODE_AES_256_CBC;
//p self._signing_key = key[:32]
_signing_key = key.left(32);
//p self._encryption_key = key[32:]
_encryption_key = key.mid(32);
}
else {
throw std::invalid_argument("Token key must be 128 or 256 bits, not " + std::to_string(key.size()*8));
}
}
else {
throw std::invalid_argument("Invalid token mode: " + std::to_string(mode));
}
MEM("Token object created");
}
Token::~Token() {
MEM("Token object destroyed");
}
bool Token::verify_hmac(const Bytes& token) {
if (token.size() <= 32) {
throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes");
}
//received_hmac = token[-32:]
Bytes received_hmac = token.right(32);
DEBUG("Token::verify_hmac: received_hmac: " + received_hmac.toHex());
//expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest();
DEBUG("Token::verify_hmac: expected_hmac: " + expected_hmac.toHex());
return (received_hmac == expected_hmac);
}
const Bytes Token::encrypt(const Bytes& data) {
DEBUG("Token::encrypt: plaintext length: " + std::to_string(data.size()));
Bytes iv = random(16);
//double current_time = OS::time();
TRACE("Token::encrypt: iv: " + iv.toHex());
TRACE("Token::encrypt: plaintext: " + data.toHex());
Bytes ciphertext;
if (_mode == MODE_AES_128_CBC) {
ciphertext = AES_128_CBC::encrypt(
PKCS7::pad(data),
_encryption_key,
iv
);
}
else if (_mode == MODE_AES_256_CBC) {
ciphertext = AES_256_CBC::encrypt(
PKCS7::pad(data),
_encryption_key,
iv
);
}
else {
throw new std::invalid_argument("Invalid token mode "+std::to_string(_mode));
}
DEBUG("Token::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size()));
TRACE("Token::encrypt: ciphertext: " + ciphertext.toHex());
Bytes signed_parts = iv + ciphertext;
//return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest();
Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest());
TRACE("Token::encrypt: sig: " + sig.toHex());
Bytes token(signed_parts + sig);
DEBUG("Token::encrypt: token length: " + std::to_string(token.size()));
return token;
}
const Bytes Token::decrypt(const Bytes& token) {
DEBUG("Token::decrypt: token length: " + std::to_string(token.size()));
if (token.size() < 48) {
throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes");
}
if (!verify_hmac(token)) {
throw std::invalid_argument("Token token HMAC was invalid");
}
//iv = token[:16]
Bytes iv = token.left(16);
TRACE("Token::decrypt: iv: " + iv.toHex());
//ciphertext = token[16:-32]
Bytes ciphertext = token.mid(16, token.size()-48);
TRACE("Token::decrypt: ciphertext: " + ciphertext.toHex());
try {
Bytes plaintext;
if (_mode == MODE_AES_128_CBC) {
plaintext = PKCS7::unpad(
AES_128_CBC::decrypt(
ciphertext,
_encryption_key,
iv
)
);
}
else if (_mode == MODE_AES_256_CBC) {
plaintext = PKCS7::unpad(
AES_256_CBC::decrypt(
ciphertext,
_encryption_key,
iv
)
);
}
else {
throw new std::invalid_argument("Invalid token mode "+std::to_string(_mode));
}
DEBUG("Token::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size()));
TRACE("Token::decrypt: plaintext: " + plaintext.toHex());
DEBUG("Token::decrypt: plaintext length: " + std::to_string(plaintext.size()));
return plaintext;
}
catch (std::exception& e) {
WARNING("Could not decrypt Token token");
throw std::runtime_error("Could not decrypt Token token");
}
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "Random.h"
#include "../Bytes.h"
#include "../Type.h"
#include <stdint.h>
namespace RNS { namespace Cryptography {
/*
This class provides a slightly modified implementation of the Fernet spec
found at: https://github.com/fernet/spec/blob/master/Spec.md
According to the spec, a Fernet token includes a one byte VERSION and
eight byte TIMESTAMP field at the start of each token. These fields are
not relevant to Reticulum. They are therefore stripped from this
implementation, since they incur overhead and leak initiator metadata.
*/
class Token {
public:
using Ptr = std::shared_ptr<Token>;
public:
static inline const Bytes generate_key(RNS::Type::Cryptography::Token::token_mode mode = RNS::Type::Cryptography::Token::MODE_AES_256_CBC) {
if (mode == RNS::Type::Cryptography::Token::MODE_AES_128_CBC) return random(32);
else if (mode == RNS::Type::Cryptography::Token::MODE_AES_256_CBC) return random(64);
else throw new std::invalid_argument("Invalid token mode: " + std::to_string(mode));
}
public:
Token(const Bytes& key, RNS::Type::Cryptography::Token::token_mode mode = RNS::Type::Cryptography::Token::MODE_AES);
~Token();
public:
bool verify_hmac(const Bytes& token);
const Bytes encrypt(const Bytes& data);
const Bytes decrypt(const Bytes& token);
private:
RNS::Type::Cryptography::Token::token_mode _mode = RNS::Type::Cryptography::Token::MODE_AES_256_CBC;
Bytes _signing_key;
Bytes _encryption_key;
};
} }

View File

@@ -0,0 +1,3 @@
#include "X25519.h"
using namespace RNS::Cryptography;

View File

@@ -0,0 +1,203 @@
#pragma once
#include "Bytes.h"
#include "Log.h"
#include <Curve25519.h>
#include <memory>
#include <stdexcept>
#include <stdint.h>
namespace RNS { namespace Cryptography {
class X25519PublicKey {
public:
using Ptr = std::shared_ptr<X25519PublicKey>;
public:
/*
X25519PublicKey(const Bytes& x) {
_x = x;
}
*/
X25519PublicKey(const Bytes& publicKey) {
_publicKey = publicKey;
}
~X25519PublicKey() {}
public:
// creates a new instance with specified seed
/*
static inline Ptr from_public_bytes(const Bytes& data) {
return Ptr(new X25519PublicKey(_unpack_number(data)));
}
*/
static inline Ptr from_public_bytes(const Bytes& publicKey) {
return Ptr(new X25519PublicKey(publicKey));
}
/*
Bytes public_bytes() {
return _pack_number(_x);
}
*/
Bytes public_bytes() {
return _publicKey;
}
private:
//Bytes _x;
Bytes _publicKey;
};
class X25519PrivateKey {
public:
const float MIN_EXEC_TIME = 2; // in milliseconds
const float MAX_EXEC_TIME = 500; // in milliseconds
const uint8_t DELAY_WINDOW = 10;
//z T_CLEAR = None
const uint8_t T_MAX = 0;
using Ptr = std::shared_ptr<X25519PrivateKey>;
public:
/*
X25519PrivateKey(const Bytes& a) {
_a = a;
}
*/
X25519PrivateKey(const Bytes& privateKey) {
if (privateKey) {
// use specified private key
_privateKey = privateKey;
// similar to derive public key from private key
// second param "f" is secret
//eval(uint8_t result[32], const uint8_t s[32], const uint8_t x[32])
// derive public key from private key
Curve25519::eval(_publicKey.writable(32), _privateKey.data(), 0);
}
else {
// create random private key and derive public key
// second param "f" is secret
//dh1(uint8_t k[32], uint8_t f[32])
Curve25519::dh1(_publicKey.writable(32), _privateKey.writable(32));
}
}
~X25519PrivateKey() {}
public:
// creates a new instance with a random seed
/*
static inline Ptr generate() {
return from_private_bytes(os.urandom(32));
}
*/
static inline Ptr generate() {
return from_private_bytes({Bytes::NONE});
}
// creates a new instance with specified seed
/*
static inline Ptr from_private_bytes(const Bytes& data) {
return Ptr(new X25519PrivateKey(_fix_secret(_unpack_number(data))));
}
*/
static inline Ptr from_private_bytes(const Bytes& privateKey) {
return Ptr(new X25519PrivateKey(privateKey));
}
/*
inline const Bytes private_bytes() {
return _pack_number(_a);
}
*/
inline const Bytes& private_bytes() {
return _privateKey;
}
// creates a new instance of public key for this private key
/*
inline X25519PublicKey::Ptr public_key() {
return X25519PublicKey::from_public_bytes(_pack_number(_raw_curve25519(9, _a)));
}
*/
inline X25519PublicKey::Ptr public_key() {
return X25519PublicKey::from_public_bytes(_publicKey);
}
/*
inline const Bytes exchange(const Bytes& peer_public_key) {
if isinstance(peer_public_key, bytes):
peer_public_key = X25519PublicKey.from_public_bytes(peer_public_key)
start = OS::time()
shared = _pack_number(_raw_curve25519(peer_public_key.x, _a))
end = OS::time()
duration = end-start
if X25519PrivateKey.T_CLEAR == None:
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
if end > X25519PrivateKey.T_CLEAR:
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
X25519PrivateKey.T_MAX = 0
if duration < X25519PrivateKey.T_MAX or duration < X25519PrivateKey.MIN_EXEC_TIME:
target = start+X25519PrivateKey.T_MAX
if target > start+X25519PrivateKey.MAX_EXEC_TIME:
target = start+X25519PrivateKey.MAX_EXEC_TIME
if target < start+X25519PrivateKey.MIN_EXEC_TIME:
target = start+X25519PrivateKey.MIN_EXEC_TIME
try:
OS::sleep(target-OS::time())
except Exception as e:
pass
elif duration > X25519PrivateKey.T_MAX:
X25519PrivateKey.T_MAX = duration
return shared
}
*/
inline const Bytes exchange(const Bytes& peer_public_key) {
DEBUG("X25519PublicKey::exchange: public key: " + _publicKey.toHex());
DEBUG("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex());
DEBUG("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex());
Bytes sharedKey;
if (!Curve25519::eval(sharedKey.writable(32), _privateKey.data(), peer_public_key.data())) {
throw std::runtime_error("Peer key is invalid");
}
DEBUG("X25519PublicKey::exchange: shared key: " + sharedKey.toHex());
DEBUG("X25519PublicKey::exchange: post private key: " + _privateKey.toHex());
return sharedKey;
}
inline bool verify(const Bytes& peer_public_key) {
DEBUG("X25519PublicKey::exchange: public key: " + _publicKey.toHex());
DEBUG("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex());
DEBUG("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex());
Bytes sharedKey(peer_public_key);
bool success = Curve25519::dh2(sharedKey.writable(32), _privateKey.writable(32));
DEBUG("X25519PublicKey::exchange: shared key: " + sharedKey.toHex());
DEBUG("X25519PublicKey::exchange: post private key: " + _privateKey.toHex());
return success;
}
private:
//Bytes _a;
Bytes _privateKey;
Bytes _publicKey;
};
} }

View File

@@ -0,0 +1,486 @@
#include "Destination.h"
#include "Transport.h"
#include "Interface.h"
#include "Packet.h"
#include "Log.h"
#include <vector>
#include <time.h>
#include <string.h>
using namespace RNS;
using namespace RNS::Type::Destination;
using namespace RNS::Utilities;
Destination::Destination(const Identity& identity, const directions direction, const types type, const char* app_name, const char* aspects) : _object(new Object(identity)) {
assert(_object);
MEM("Destination object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
// Check input values and build name string
if (strchr(app_name, '.') != nullptr) {
throw std::invalid_argument("Dots can't be used in app names");
}
//TRACE("Destination::Destination: app name: " + std::string(app_name));
_object->_type = type;
_object->_direction = direction;
std::string fullaspects(aspects);
if (!identity && direction == IN && _object->_type != PLAIN) {
TRACE("Destination::Destination: identity not provided, creating new one");
_object->_identity = Identity();
// CBA TODO determine why identity.hexhash is added both here and by expand_name called below
fullaspects += "." + _object->_identity.hexhash();
}
//TRACE("Destination::Destination: full aspects: " + fullaspects);
if (_object->_identity && _object->_type == PLAIN) {
throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity");
}
_object->_name = expand_name(_object->_identity, app_name, fullaspects.c_str());
//TRACE("Destination::Destination: name: " + _object->_name);
// Generate the destination address hash
//TRACE("Destination::Destination: creating hash...");
_object->_hash = hash(_object->_identity, app_name, fullaspects.c_str());
_object->_hexhash = _object->_hash.toHex();
TRACE("Destination::Destination: hash: " + _object->_hash.toHex());
//TRACE("Destination::Destination: creating name hash...");
//p self.name_hash = RNS.Identity.full_hash(self.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
_object->_name_hash = name_hash(app_name, aspects);
//TRACE("Destination::Destination: name hash: " + _object->_name_hash.toHex());
//TRACE("Destination::Destination: calling register_destination");
Transport::register_destination(*this);
MEM("Destination object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
Destination::Destination(const Identity& identity, const Type::Destination::directions direction, const Type::Destination::types type, const Bytes& hash) : _object(new Object(identity)) {
assert(_object);
MEM("Destination object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
_object->_type = type;
_object->_direction = direction;
if (_object->_identity && _object->_type == PLAIN) {
throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity");
}
_object->_hash = hash;
_object->_hexhash = _object->_hash.toHex();
TRACE("Destination::Destination: hash: " + _object->_hash.toHex());
//TRACE("Destination::Destination: creating name hash...");
//p self.name_hash = RNS.Identity.full_hash(self.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
_object->_name_hash = name_hash("unknown", "unknown");
//TRACE("Destination::Destination: name hash: " + _object->_name_hash.toHex());
//TRACE("Destination::Destination: calling register_destination");
Transport::register_destination(*this);
MEM("Destination object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
/*virtual*/ Destination::~Destination() {
MEM("Destination object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
if (_object && _object.use_count() == 1) {
MEM("Destination object has last data reference");
// CBA Can't call deregister_destination here because it's possible (likely even) that Destination
// is being destructed from that same collection which will result in a llop and memory errors.
//TRACE("Destination::~Destination: calling deregister_destination");
//Transport::deregister_destination(*this);
}
}
/*
:returns: A destination name in adressable hash form, for an app_name and a number of aspects.
*/
/*static*/ Bytes Destination::hash(const Identity& identity, const char* app_name, const char* aspects) {
//p name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
//p addr_hash_material = name_hash
Bytes addr_hash_material = name_hash(app_name, aspects);
if (identity) {
addr_hash_material << identity.hash();
}
//p return RNS.Identity.full_hash(addr_hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
// CBA TODO valid alternative?
//return Identity::full_hash(addr_hash_material).left(Type::Reticulum::TRUNCATED_HASHLENGTH/8);
return Identity::truncated_hash(addr_hash_material);
}
/*
:returns: A name in hash form, for an app_name and a number of aspects.
*/
/*static*/ Bytes Destination::name_hash(const char* app_name, const char* aspects) {
//p name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
return Identity::full_hash(expand_name({Type::NONE}, app_name, aspects)).left(Type::Identity::NAME_HASH_LENGTH/8);
}
/*
:returns: A tuple containing the app name and a list of aspects, for a full-name string.
*/
/*static*/ std::vector<std::string> Destination::app_and_aspects_from_name(const char* full_name) {
//p components = full_name.split(".")
//p return (components[0], components[1:])
std::vector<std::string> components;
std::string name(full_name);
std::size_t pos = name.find('.');
components.push_back(name.substr(0, pos));
if (pos != std::string::npos) {
components.push_back(name.substr(pos+1));
}
return components;
}
/*
:returns: A destination name in adressable hash form, for a full name string and Identity instance.
*/
/*static*/ Bytes Destination::hash_from_name_and_identity(const char* full_name, const Identity& identity) {
//p app_name, aspects = Destination.app_and_aspects_from_name(full_name)
//p return Destination.hash(identity, app_name, *aspects)
std::vector<std::string> components = app_and_aspects_from_name(full_name);
if (components.size() == 0) {
return {Bytes::NONE};
}
if (components.size() == 1) {
return hash(identity, components[0].c_str(), "");
}
return hash(identity, components[0].c_str(), components[1].c_str());
}
/*
:returns: A string containing the full human-readable name of the destination, for an app_name and a number of aspects.
*/
/*static*/ std::string Destination::expand_name(const Identity& identity, const char* app_name, const char* aspects) {
if (strchr(app_name, '.') != nullptr) {
throw std::invalid_argument("Dots can't be used in app names");
}
std::string name(app_name);
if (aspects != nullptr) {
name += std::string(".") + aspects;
}
if (identity) {
name += "." + identity.hexhash();
}
return name;
}
/*
Creates an announce packet for this destination and broadcasts it on all
relevant interfaces. Application specific data can be added to the announce.
:param app_data: *bytes* containing the app_data.
:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
*/
//Packet Destination::announce(const Bytes& app_data /*= {}*/, bool path_response /*= false*/, const Interface& attached_interface /*= {Type::NONE}*/, const Bytes& tag /*= {}*/, bool send /*= true*/) {
Packet Destination::announce(const Bytes& app_data, bool path_response, const Interface& attached_interface, const Bytes& tag /*= {}*/, bool send /*= true*/) {
assert(_object);
TRACE("Destination::announce: announcing destination...");
if (_object->_type != SINGLE) {
throw std::invalid_argument("Only SINGLE destination types can be announced");
}
if (_object->_direction != IN) {
throw std::invalid_argument("Only IN destination types can be announced");
}
double now = OS::time();
auto it = _object->_path_responses.begin();
while (it != _object->_path_responses.end()) {
// vector
//Response& entry = *it;
// map
PathResponse& entry = (*it).second;
if (now > (entry.first + PR_TAG_WINDOW)) {
it = _object->_path_responses.erase(it);
}
else {
++it;
}
}
Bytes announce_data;
/*
// CBA TEST
TRACE("Destination::announce: performing path test...");
TRACE("Destination::announce: inserting path...");
_object->_path_responses.insert({Bytes("foo_tag"), {0, Bytes("this is foo tag")}});
TRACE("Destination::announce: inserting path...");
_object->_path_responses.insert({Bytes("test_tag"), {0, Bytes("this is test tag")}});
if (path_response) {
TRACE("Destination::announce: path_response is true");
}
if (!tag.empty()) {
TRACE("Destination::announce: tag is specified");
std::string tagstr((const char*)tag.data(), tag.size());
DEBUG(std::string("Destination::announce: tag: ") + tagstr);
DEBUG(std::string("Destination::announce: tag len: ") + std::to_string(tag.size()));
TRACE("Destination::announce: searching for tag...");
if (_object->_path_responses.find(tag) != _object->_path_responses.end()) {
TRACE("Destination::announce: found tag in _path_responses");
DEBUG(std::string("Destination::announce: data: ") +_object->_path_responses[tag].second.toString());
}
else {
TRACE("Destination::announce: tag not found in _path_responses");
}
}
TRACE("Destination::announce: path test finished");
*/
if (path_response && !tag.empty() && _object->_path_responses.find(tag) != _object->_path_responses.end()) {
// This code is currently not used, since Transport will block duplicate
// path requests based on tags. When multi-path support is implemented in
// Transport, this will allow Transport to detect redundant paths to the
// same destination, and select the best one based on chosen criteria,
// since it will be able to detect that a single emitted announce was
// received via multiple paths. The difference in reception time will
// potentially also be useful in determining characteristics of the
// multiple available paths, and to choose the best one.
//z TRACE("Using cached announce data for answering path request with tag "+RNS.prettyhexrep(tag));
announce_data << _object->_path_responses[tag].second;
}
else {
Bytes destination_hash = _object->_hash;
//p random_hash = Identity::get_random_hash()[0:5] << int(time.time()).to_bytes(5, "big")
// CBA TODO add in time to random hash
Bytes random_hash = Cryptography::random(Type::Identity::RANDOM_HASH_LENGTH/8);
Bytes new_app_data(app_data);
if (new_app_data.empty() && !_object->_default_app_data.empty()) {
new_app_data = _object->_default_app_data;
}
Bytes signed_data;
//TRACE("Destination::announce: hash: " + _object->_hash.toHex());
//TRACE("Destination::announce: public key: " + _object->_identity.get_public_key().toHex());
//TRACE("Destination::announce: name hash: " + _object->_name_hash.toHex());
//TRACE("Destination::announce: random hash: " + random_hash.toHex());
//TRACE("Destination::announce: app data: " + new_app_data.toHex());
//TRACE("Destination::announce: app data text:" + new_app_data.toString());
signed_data << _object->_hash << _object->_identity.get_public_key() << _object->_name_hash << random_hash;
if (new_app_data) {
signed_data << new_app_data;
}
//TRACE("Destination::announce: signed data: " + signed_data.toHex());
Bytes signature(_object->_identity.sign(signed_data));
//TRACE("Destination::announce: signature: " + signature.toHex());
announce_data << _object->_identity.get_public_key() << _object->_name_hash << random_hash << signature;
if (new_app_data) {
announce_data << new_app_data;
}
// CBA ACCUMULATES
_object->_path_responses.insert({tag, {OS::time(), announce_data}});
}
//TRACE("Destination::announce: announce_data:" + announce_data.toHex());
Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
if (path_response) {
announce_context = Type::Packet::PATH_RESPONSE;
}
//TRACE("Destination::announce: creating announce packet...");
//p announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
//Packet announce_packet(*this, announce_data, Type::Packet::ANNOUNCE, announce_context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, nullptr, attached_interface);
Packet announce_packet(*this, attached_interface, announce_data, Type::Packet::ANNOUNCE, announce_context, Type::Transport::BROADCAST, Type::Packet::HEADER_1);
if (send) {
TRACE("Destination::announce: sending announce packet...");
announce_packet.send();
return {Type::NONE};
}
else {
return announce_packet;
}
}
Packet Destination::announce(const Bytes& app_data /*= {}*/, bool path_response /*= false*/) {
return announce(app_data, path_response, {Type::NONE});
}
/*
Registers a request handler.
:param path: The path for the request handler to be registered.
: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.
:raises: ``ValueError`` if any of the supplied arguments are invalid.
*/
/*
void Destination::register_request_handler(const Bytes& path, response_generator = None, request_policies allow = ALLOW_NONE, allowed_list = None) {
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]
self.request_handlers[path_hash] = request_handler
}
*/
/*
Deregisters a request handler.
:param path: The path for the request handler to be deregistered.
:returns: True if the handler was deregistered, otherwise False.
*/
/*
bool Destination::deregister_request_handler(const Bytes& path) {
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
if path_hash in self.request_handlers:
self.request_handlers.pop(path_hash)
return True
else:
return False
}
*/
void Destination::receive(const Packet& packet) {
assert(_object);
if (packet.packet_type() == Type::Packet::LINKREQUEST) {
Bytes plaintext(packet.data());
incoming_link_request(plaintext, packet);
}
else {
// CBA TODO Why isn't the Packet decrypting itself?
Bytes plaintext(decrypt(packet.data()));
//TRACE("Destination::receive: decrypted data: " + plaintext.toHex());
if (plaintext) {
if (packet.packet_type() == Type::Packet::DATA) {
if (_object->_callbacks._packet) {
try {
_object->_callbacks._packet(plaintext, packet);
}
catch (std::exception& e) {
DEBUG("Error while executing receive callback from " + toString() + ". The contained exception was: " + e.what());
}
}
}
}
}
}
void Destination::incoming_link_request(const Bytes& data, const Packet& packet) {
assert(_object);
if (_object->_accept_link_requests) {
TRACE("***** Accepting link request");
RNS::Link link = Link::validate_request(*this, data, packet);
if (link) {
_object->_links.insert(link);
}
}
}
/*
Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
:param plaintext: A *bytes-like* containing the plaintext to be encrypted.
:raises: ``ValueError`` if destination does not hold a necessary key for encryption.
*/
/*virtual*/ const Bytes Destination::encrypt(const Bytes& data) {
assert(_object);
TRACE("Destination::encrypt: encrypting data...");
if (_object->_type == PLAIN) {
return data;
}
if (_object->_type == SINGLE && _object->_identity) {
return _object->_identity.encrypt(data);
}
// TODO
/*
if (_object->_type == GROUP {
if hasattr(self, "prv") and self.prv != None:
try:
return self.prv.encrypt(plaintext)
except Exception as e:
RNS.log("The GROUP destination could not encrypt data", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
}
*/
// CBA Reference implementation does not handle this default case
// CBA TODO Determine of returning plaintext is appropriate here (for now it's necessary for PROOF packets)
return data;
}
/*
Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
:param ciphertext: *Bytes* containing the ciphertext to be decrypted.
:raises: ``ValueError`` if destination does not hold a necessary key for decryption.
*/
/*virtual*/ const Bytes Destination::decrypt(const Bytes& data) {
assert(_object);
TRACE("Destination::decrypt: decrypting data...");
if (_object->_type == PLAIN) {
return data;
}
if (_object->_type == SINGLE && _object->_identity) {
return _object->_identity.decrypt(data);
}
/*
if (_object->_type == GROUP) {
if hasattr(self, "prv") and self.prv != None:
try:
return self.prv.decrypt(ciphertext)
except Exception as e:
RNS.log("The GROUP destination could not decrypt data", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
}
*/
// MOCK
return {Bytes::NONE};
}
/*
Signs information for ``RNS.Destination.SINGLE`` type destination.
:param message: *Bytes* containing the message to be signed.
:returns: A *bytes-like* containing the message signature, or *None* if the destination could not sign the message.
*/
/*virtual*/ const Bytes Destination::sign(const Bytes& message) {
assert(_object);
if (_object->_type == SINGLE && _object->_identity) {
return _object->_identity.sign(message);
}
return {Bytes::NONE};
}
bool Destination::has_link(const Link& link) {
assert(_object);
return (_object->_links.count(link) > 0);
}
void Destination::remove_link(const Link& link) {
assert(_object);
_object->_links.erase(link);
}

View File

@@ -0,0 +1,259 @@
#pragma once
//#include "Reticulum.h"
//#include "Link.h"
//#include "Interface.h"
#include "Identity.h"
#include "Bytes.h"
#include "Type.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <map>
#include <set>
#include <cassert>
#include <stdint.h>
namespace RNS {
class Interface;
class Link;
class Packet;
class RequestHandler {
public:
//p response_generator(path, data, request_id, link_id, remote_identity, requested_at)
using response_generator = Bytes(*)(const Bytes& path, const Bytes& data, const Bytes& request_id, const Bytes& link_id, const Identity& remote_identity, double requested_at);
public:
RequestHandler(const RequestHandler& handler) {
_path = handler._path;
_response_generator = handler._response_generator;
_allow = handler._allow;
_allowed_list = handler._allowed_list;
}
Bytes _path;
response_generator _response_generator = nullptr;
Type::Destination::request_policies _allow = Type::Destination::ALLOW_NONE;
std::set<Bytes> _allowed_list;
};
/**
* @brief A class used to describe endpoints in a Reticulum Network. Destination
* instances are used both to create outgoing and incoming endpoints. The
* destination type will decide if encryption, and what type, is used in
* communication with the endpoint. A destination can also announce its
* presence on the network, which will also distribute necessary keys for
* encrypted communication with it.
*
* @param identity An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
* @param direction ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
* @param type ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
* @param app_name A string specifying the app name.
* @param aspects Any non-zero number of string arguments.
*/
class Destination {
public:
class Callbacks {
public:
using link_established = void(*)(Link& link);
//using packet = void(*)(uint8_t* data, uint16_t data_len, Packet *packet);
using packet = void(*)(const Bytes& data, const Packet& packet);
using proof_requested = bool(*)(const Packet& packet);
public:
link_established _link_established = nullptr;
packet _packet = nullptr;
proof_requested _proof_requested = nullptr;
friend class Destination;
};
using PathResponse = std::pair<double, Bytes>;
public:
Destination(Type::NoneConstructor none) {
MEM("Destination NONE object created, this: " + std::to_string((uintptr_t)this));
}
Destination(const Destination& destination) : _object(destination._object) {
MEM("Destination object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
Destination(
const Identity& identity,
const Type::Destination::directions direction,
const Type::Destination::types type,
const char* app_name,
const char* aspects
);
Destination(
const Identity& identity,
const Type::Destination::directions direction,
const Type::Destination::types type,
const Bytes& hash
);
virtual ~Destination();
inline Destination& operator = (const Destination& destination) {
_object = destination._object;
MEM("Destination object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
return *this;
}
inline operator bool() const {
return _object.get() != nullptr;
}
inline bool operator < (const Destination& destination) const {
return _object.get() < destination._object.get();
}
public:
static std::string expand_name(const Identity& identity, const char* app_name, const char* aspects);
static Bytes hash(const Identity& identity, const char* app_name, const char* aspects);
static Bytes name_hash(const char* app_name, const char* aspects);
static std::vector<std::string> app_and_aspects_from_name(const char* full_name);
static Bytes hash_from_name_and_identity(const char* full_name, const Identity& identity);
public:
//Packet announce(const Bytes& app_data = {}, bool path_response = false, const Interface& attached_interface = {Type::NONE}, const Bytes& tag = {}, bool send = true);
Packet announce(const Bytes& app_data, bool path_response, const Interface& attached_interface, const Bytes& tag = {}, bool send = true);
Packet announce(const Bytes& app_data = {}, bool path_response = false);
/*
Set or query whether the destination accepts incoming link requests.
:param accepts: If ``True`` or ``False``, this method sets whether the destination accepts incoming link requests. If not provided or ``None``, the method returns whether the destination currently accepts link requests.
:returns: ``True`` or ``False`` depending on whether the destination accepts incoming link requests, if the *accepts* parameter is not provided or ``None``.
*/
inline void accepts_links(bool accepts) { assert(_object); _object->_accept_link_requests = accepts; }
inline bool accepts_links() { assert(_object); return _object->_accept_link_requests; }
/*
Registers a function to be called when a link has been established to
this destination.
:param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination.
*/
inline void set_link_established_callback(Callbacks::link_established callback) {
assert(_object);
_object->_callbacks._link_established = callback;
}
/*
Registers a function to be called when a packet has been received by
this destination.
:param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet.
*/
inline void set_packet_callback(Callbacks::packet callback) {
assert(_object);
_object->_callbacks._packet = callback;
}
/*
Registers a function to be called when a proof has been requested for
a packet sent to this destination. Allows control over when and if
proofs should be returned for received packets.
:param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
*/
inline void set_proof_requested_callback(Callbacks::proof_requested callback) {
assert(_object);
_object->_callbacks._proof_requested = callback;
}
/*
Sets the destinations proof strategy.
:param proof_strategy: One of ``RNS.Destination.PROVE_NONE``, ``RNS.Destination.PROVE_ALL`` or ``RNS.Destination.PROVE_APP``. If ``RNS.Destination.PROVE_APP`` is set, the `proof_requested_callback` will be called to determine whether a proof should be sent or not.
*/
inline void set_proof_strategy(Type::Destination::proof_strategies proof_strategy) {
assert(_object);
//if (proof_strategy <= PROOF_NONE) {
// throw throw std::invalid_argument("Unsupported proof strategy");
//}
_object->_proof_strategy = proof_strategy;
}
void receive(const Packet& packet);
void incoming_link_request(const Bytes& data, const Packet& packet);
virtual const Bytes encrypt(const Bytes& data);
virtual const Bytes decrypt(const Bytes& data);
virtual const Bytes sign(const Bytes& message);
// CBA
bool has_link(const Link& link);
void remove_link(const Link& link);
inline std::string toString() const { if (!_object) return ""; return "{Destination:" + _object->_hash.toHex() + "}"; }
// getters
inline Type::Destination::types type() const { assert(_object); return _object->_type; }
inline Type::Destination::directions direction() const { assert(_object); return _object->_direction; }
inline Type::Destination::proof_strategies proof_strategy() const { assert(_object); return _object->_proof_strategy; }
inline const Bytes& hash() const { assert(_object); return _object->_hash; }
inline uint16_t mtu() const { assert(_object); return _object->_mtu; }
// CBA LINK
//inline const Bytes& link_id() const { assert(_object); return _object->_link_id; }
//inline Type::Link::status status() const { assert(_object); return _object->_status; }
inline const Callbacks& callbacks() const { assert(_object); return _object->_callbacks; }
inline const Identity& identity() const { assert(_object); return _object->_identity; }
inline const std::map<Bytes, PathResponse>& path_responses() const { assert(_object); return _object->_path_responses; }
inline const std::map<Bytes, RequestHandler>& request_handlers() const { assert(_object); return _object->_request_handlers; }
// setters
// CBA Don't allow changing destination hash after construction since it's used as key in collections
//inline void hash(const Bytes& hash) { assert(_object); _object->_hash = hash; _object->_hexhash = _object->_hash.toHex(); }
inline void type(Type::Destination::types type) { assert(_object); _object->_type = type; }
inline void mtu(uint16_t mtu) { assert(_object); _object->_mtu = mtu; }
// CBA LINK
//inline void link_id(const Bytes& id) { assert(_object); _object->_link_id = id; }
//inline void last_outbound(double time) { assert(_object); _object->_last_outbound = time; }
//inline void increment_tx() { assert(_object); ++_object->_tx; }
//inline void increment_txbytes(uint16_t bytes) { assert(_object); _object->_txbytes += bytes; }
private:
class Object {
public:
Object(const Identity& identity) : _identity(identity) { MEM("Destination::Data object created, this: " + std::to_string((uintptr_t)this)); }
virtual ~Object() { MEM("Destination::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
private:
bool _accept_link_requests = true;
Callbacks _callbacks;
std::map<Bytes, RequestHandler> _request_handlers;
Type::Destination::types _type;
Type::Destination::directions _direction;
Type::Destination::proof_strategies _proof_strategy = Type::Destination::PROVE_NONE;
uint16_t _mtu = 0;
std::map<Bytes, PathResponse> _path_responses;
std::set<Link> _links;
Identity _identity;
std::string _name;
// Generate the destination address hash
Bytes _hash;
Bytes _name_hash;
std::string _hexhash;
// CBA TODO when is _default_app_data a "callable"?
Bytes _default_app_data;
//z _callback = None
//z _proofcallback = None
// CBA LINK
// CBA _link_id is expected by Packet but only present in Link
// CBA TODO determine if Link needs to inherit from Destination or vice-versa
//Bytes _link_id;
//Type::Link::status _status;
//double _last_outbound = 0.0;
//uint16_t _tx = 0;
//uint32_t _txbytes = 0;
friend class Destination;
};
std::shared_ptr<Object> _object;
};
}

View File

@@ -0,0 +1,144 @@
#pragma once
#include "Utilities/Crc.h"
#include "Log.h"
#include "Bytes.h"
#include "Type.h"
#ifdef ARDUINO
#include <Stream.h>
#else
#include "Utilities/Stream.h"
#endif
#include <list>
#include <memory>
#include <cassert>
#include <stdint.h>
namespace RNS {
class FileStreamImpl {
protected:
FileStreamImpl() { MEMF("FileStreamImpl object created, this: 0x%X", this); }
public:
virtual ~FileStreamImpl() { MEMF("FileStreamImpl object destroyed, this: 0x%X", this); }
protected:
// CBA TODO Can these be pure-virtual???
virtual const char* name() = 0;
virtual size_t size() = 0;
virtual void close() = 0;
// Print overrides
virtual size_t write(uint8_t byte) = 0;
virtual size_t write(const uint8_t *buffer, size_t size) = 0;
// Stream overrides
virtual int available() = 0;
virtual int read() = 0;
virtual int peek() = 0;
virtual void flush() = 0;
friend class FileStream;
};
class FileStream : public Stream {
public:
enum MODE {
MODE_READ,
MODE_WRITE,
MODE_APPEND,
};
public:
FileStream(Type::NoneConstructor none) {
MEMF("FileStream NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
FileStream(const FileStream& obj) : _impl(obj._impl), _crc(obj._crc) {
MEMF("FileStream object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
FileStream(FileStreamImpl* impl) : _impl(impl) {
MEMF("FileStream object impl created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
virtual ~FileStream() {
MEMF("FileStream object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
}
inline virtual FileStream& operator = (const FileStream& obj) {
_impl = obj._impl;
_crc = obj._crc;
MEMF("FileStream object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
return *this;
}
inline FileStream& operator = (FileStreamImpl* impl) {
_impl.reset(impl);
MEMF("FileStream object copy created by impl assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
return *this;
}
inline operator bool() const {
MEMF("FileStream object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() != nullptr;
}
inline bool operator < (const FileStream& obj) const {
MEMF("FileStream object <, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() < obj._impl.get();
}
inline bool operator > (const FileStream& obj) const {
MEMF("FileStream object <, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() > obj._impl.get();
}
inline bool operator == (const FileStream& obj) const {
MEMF("FileStream object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() == obj._impl.get();
}
inline bool operator != (const FileStream& obj) const {
MEMF("FileStream object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() != obj._impl.get();
}
inline FileStreamImpl* get() {
return _impl.get();
}
inline void clear() {
_impl.reset();
}
public:
inline uint32_t crc() { return _crc; }
inline size_t write(const char* str) { return write((const uint8_t*)str, strlen(str)); }
// File overrides
inline const char* name() { assert(_impl); return _impl->name(); }
inline size_t size() { assert(_impl); return _impl->size(); }
inline void close() { assert(_impl); _impl->close(); }
// Print overrides
inline size_t write(uint8_t byte) { assert(_impl); _crc = Utilities::Crc::crc32(_crc, byte); return _impl->write(byte); }
inline size_t write(const uint8_t *buffer, size_t size) { assert(_impl); _crc = Utilities::Crc::crc32(_crc, buffer, size); return _impl->write(buffer, size); }
// Stream overrides
inline int available() { assert(_impl); return _impl->available(); }
inline int read() { assert(_impl); if (_impl->available() <= 0) return EOF; int ch = _impl->read(); uint8_t byte = (uint8_t)ch; _crc = Utilities::Crc::crc32(_crc, byte); return ch; }
inline int peek() { assert(_impl); return _impl->peek(); }
inline void flush() { assert(_impl); _impl->flush(); }
// getters/setters
protected:
public:
#ifndef NDEBUG
inline std::string debugString() const {
std::string dump;
dump = "FileStream object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
return dump;
}
#endif
protected:
std::shared_ptr<FileStreamImpl> _impl;
uint32_t _crc = 0;
};
}

View File

@@ -0,0 +1,129 @@
#pragma once
#include "FileStream.h"
#include "Log.h"
#include "Bytes.h"
#include "Type.h"
#include <list>
#include <memory>
#include <cassert>
#include <stdint.h>
namespace RNS {
class FileSystemImpl {
protected:
FileSystemImpl() { MEMF("FileSystem::FileSystemImpl object created, this: 0x%X", this); }
public:
virtual ~FileSystemImpl() { MEMF("FileSystem::FileSystemImpl object destroyed, this: 0x%X", this); }
protected:
virtual bool init() { return true; }
virtual void loop() {}
virtual bool file_exists(const char* file_path) = 0;
virtual size_t read_file(const char* file_path, Bytes& data) = 0;
virtual size_t write_file(const char* file_path, const Bytes& data) = 0;
virtual FileStream open_file(const char* file_path, FileStream::MODE file_mode) = 0;
virtual bool remove_file(const char* file_path) = 0;
virtual bool rename_file(const char* from_file_path, const char* to_file_path) = 0;
virtual bool directory_exists(const char* directory_path) = 0;
virtual bool create_directory(const char* directory_path) = 0;
virtual bool remove_directory(const char* directory_path) = 0;
virtual std::list<std::string> list_directory(const char* directory_path) = 0;
virtual size_t storage_size() = 0;
virtual size_t storage_available() = 0;
friend class FileSystem;
};
class FileSystem {
public:
FileSystem(Type::NoneConstructor none) {
MEMF("FileSystem NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
FileSystem(const FileSystem& obj) : _impl(obj._impl) {
MEMF("FileSystem object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
FileSystem(FileSystemImpl* impl) : _impl(impl) {
MEMF("FileSystem object impl created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
virtual ~FileSystem() {
MEMF("FileSystem object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
}
inline FileSystem& operator = (const FileSystem& obj) {
_impl = obj._impl;
MEMF("FileSystem object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
return *this;
}
inline FileSystem& operator = (FileSystemImpl* impl) {
_impl.reset(impl);
MEMF("FileSystem object copy created by impl assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
return *this;
}
inline operator bool() const {
MEMF("FileSystem object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() != nullptr;
}
inline bool operator < (const FileSystem& obj) const {
MEMF("FileSystem object <, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() < obj._impl.get();
}
inline bool operator > (const FileSystem& obj) const {
MEMF("FileSystem object <, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() > obj._impl.get();
}
inline bool operator == (const FileSystem& obj) const {
MEMF("FileSystem object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() == obj._impl.get();
}
inline bool operator != (const FileSystem& obj) const {
MEMF("FileSystem object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() != obj._impl.get();
}
inline FileSystemImpl* get() {
return _impl.get();
}
inline void clear() {
_impl.reset();
}
public:
inline bool init() { assert(_impl); return _impl->init(); }
inline void loop() { assert(_impl); return _impl->loop(); }
inline bool file_exists(const char* file_path) { assert(_impl); return _impl->file_exists(file_path); }
inline size_t read_file(const char* file_path, Bytes& data) { assert(_impl); return _impl->read_file(file_path, data); }
inline size_t write_file(const char* file_path, const Bytes& data) { assert(_impl); return _impl->write_file(file_path, data); }
inline FileStream open_file(const char* file_path, FileStream::MODE file_mode) { return _impl->open_file(file_path, file_mode); }
inline bool remove_file(const char* file_path) { assert(_impl); return _impl->remove_file(file_path); }
inline bool rename_file(const char* from_file_path, const char* to_file_path) { assert(_impl); return _impl->rename_file(from_file_path, to_file_path); }
inline bool directory_exists(const char* directory_path) { assert(_impl); return _impl->directory_exists(directory_path); }
inline bool create_directory(const char* directory_path) { assert(_impl); return _impl->create_directory(directory_path); }
inline bool remove_directory(const char* directory_path) { assert(_impl); return _impl->remove_directory(directory_path); }
inline std::list<std::string> list_directory(const char* directory_path) { assert(_impl); return _impl->list_directory(directory_path); }
inline size_t storage_size() { assert(_impl); return _impl->storage_size(); }
inline size_t storage_available() { assert(_impl); return _impl->storage_available(); }
private:
std::list<std::string> _empty;
// getters/setters
protected:
public:
#ifndef NDEBUG
inline std::string debugString() const {
std::string dump;
dump = "FileSystem object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
return dump;
}
#endif
protected:
std::shared_ptr<FileSystemImpl> _impl;
};
}

View File

@@ -0,0 +1,661 @@
#include "Identity.h"
#include "Reticulum.h"
#include "Transport.h"
#include "Packet.h"
#include "Log.h"
#include "Utilities/OS.h"
#include "Cryptography/Ed25519.h"
#include "Cryptography/X25519.h"
#include "Cryptography/HKDF.h"
#include "Cryptography/Token.h"
#include "Cryptography/Random.h"
#include <algorithm>
#include <string.h>
using namespace RNS;
using namespace RNS::Type::Identity;
using namespace RNS::Cryptography;
using namespace RNS::Utilities;
/*static*/ std::map<Bytes, Identity::IdentityEntry> Identity::_known_destinations;
/*static*/ bool Identity::_saving_known_destinations = false;
// CBA
// CBA ACCUMULATES
/*static*/ //uint16_t Identity::_known_destinations_maxsize = 100;
/*static*/ uint16_t Identity::_known_destinations_maxsize = 100;
Identity::Identity(bool create_keys /*= true*/) : _object(new Object()) {
if (create_keys) {
createKeys();
}
MEM("Identity object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
void Identity::createKeys() {
assert(_object);
// CRYPTO: create encryption private keys
_object->_prv = Cryptography::X25519PrivateKey::generate();
_object->_prv_bytes = _object->_prv->private_bytes();
//TRACE("Identity::createKeys: prv bytes: " + _object->_prv_bytes.toHex());
// CRYPTO: create signature private keys
_object->_sig_prv = Cryptography::Ed25519PrivateKey::generate();
_object->_sig_prv_bytes = _object->_sig_prv->private_bytes();
//TRACE("Identity::createKeys: sig prv bytes: " + _object->_sig_prv_bytes.toHex());
// CRYPTO: create encryption public keys
_object->_pub = _object->_prv->public_key();
_object->_pub_bytes = _object->_pub->public_bytes();
//TRACE("Identity::createKeys: pub bytes: " + _object->_pub_bytes.toHex());
// CRYPTO: create signature public keys
_object->_sig_pub = _object->_sig_prv->public_key();
_object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
//TRACE("Identity::createKeys: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
update_hashes();
VERBOSE("Identity keys created for " + _object->_hash.toHex());
}
/*
Load a private key into the instance.
:param prv_bytes: The private key as *bytes*.
:returns: True if the key was loaded, otherwise False.
*/
bool Identity::load_private_key(const Bytes& prv_bytes) {
assert(_object);
try {
//p self.prv_bytes = prv_bytes[:Identity.KEYSIZE//8//2]
_object->_prv_bytes = prv_bytes.left(Type::Identity::KEYSIZE/8/2);
_object->_prv = X25519PrivateKey::from_private_bytes(_object->_prv_bytes);
//TRACE("Identity::load_private_key: prv bytes: " + _object->_prv_bytes.toHex());
//p self.sig_prv_bytes = prv_bytes[Identity.KEYSIZE//8//2:]
_object->_sig_prv_bytes = prv_bytes.mid(Type::Identity::KEYSIZE/8/2);
_object->_sig_prv = Ed25519PrivateKey::from_private_bytes(_object->_sig_prv_bytes);
//TRACE("Identity::load_private_key: sig prv bytes: " + _object->_sig_prv_bytes.toHex());
_object->_pub = _object->_prv->public_key();
_object->_pub_bytes = _object->_pub->public_bytes();
//TRACE("Identity::load_private_key: pub bytes: " + _object->_pub_bytes.toHex());
_object->_sig_pub = _object->_sig_prv->public_key();
_object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
//TRACE("Identity::load_private_key: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
update_hashes();
return true;
}
catch (std::exception& e) {
//p raise e
ERROR("Failed to load identity key");
ERRORF("The contained exception was: %s", e.what());
return false;
}
}
/*
Load a public key into the instance.
:param pub_bytes: The public key as *bytes*.
:returns: True if the key was loaded, otherwise False.
*/
void Identity::load_public_key(const Bytes& pub_bytes) {
assert(_object);
try {
//_pub_bytes = pub_bytes[:Identity.KEYSIZE//8//2]
_object->_pub_bytes = pub_bytes.left(Type::Identity::KEYSIZE/8/2);
//TRACE("Identity::load_public_key: pub bytes: " + _object->_pub_bytes.toHex());
//_sig_pub_bytes = pub_bytes[Identity.KEYSIZE//8//2:]
_object->_sig_pub_bytes = pub_bytes.mid(Type::Identity::KEYSIZE/8/2);
//TRACE("Identity::load_public_key: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
_object->_pub = X25519PublicKey::from_public_bytes(_object->_pub_bytes);
_object->_sig_pub = Ed25519PublicKey::from_public_bytes(_object->_sig_pub_bytes);
update_hashes();
}
catch (std::exception& e) {
ERRORF("Error while loading public key, the contained exception was: %s", e.what());
}
}
bool Identity::load(const char* path) {
TRACE("Reading identity key from storage...");
#if defined(RNS_USE_FS)
try {
Bytes prv_bytes;
if (OS::read_file(path, prv_bytes) > 0) {
return load_private_key(prv_bytes);
}
else {
return false;
}
}
catch (std::exception& e) {
ERROR("Error while loading identity from " + std::string(path));
ERRORF("The contained exception was: %s", e.what());
}
#endif
return false;
}
/*
Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.
:param path: The full path specifying where to save the identity.
:returns: True if the file was saved, otherwise False.
*/
bool Identity::to_file(const char* path) {
TRACE("Writing identity key to storage...");
#if defined(RNS_USE_FS)
try {
return (OS::write_file(path, get_private_key()) == get_private_key().size());
}
catch (std::exception& e) {
ERRORF("Error while saving identity to %s", path);
ERRORF("The contained exception was: %s", e.what());
}
#endif
return false;
}
/*
Create a new :ref:`RNS.Identity<api-identity>` instance from a file.
Can be used to load previously created and saved identities into Reticulum.
:param path: The full path to the saved :ref:`RNS.Identity<api-identity>` data
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the loaded data was invalid.
*/
/*static*/ const Identity Identity::from_file(const char* path) {
Identity identity(false);
if (identity.load(path)) {
return identity;
}
return {Type::NONE};
}
/*static*/ void Identity::remember(const Bytes& packet_hash, const Bytes& destination_hash, const Bytes& public_key, const Bytes& app_data /*= {Bytes::NONE}*/) {
if (public_key.size() != Type::Identity::KEYSIZE/8) {
throw std::invalid_argument("Can't remember " + destination_hash.toHex() + ", the public key size of " + std::to_string(public_key.size()) + " is not valid.");
}
else {
//p _known_destinations[destination_hash] = {OS::time(), packet_hash, public_key, app_data};
// CBA ACCUMULATES
_known_destinations.insert({destination_hash, {OS::time(), packet_hash, public_key, app_data}});
}
}
/*
Recall identity for a destination hash.
:param destination_hash: Destination hash as *bytes*.
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
*/
/*static*/ Identity Identity::recall(const Bytes& destination_hash) {
TRACE("Identity::recall...");
auto iter = _known_destinations.find(destination_hash);
if (iter != _known_destinations.end()) {
TRACE("Identity::recall: Found identity entry for destination " + destination_hash.toHex());
const IdentityEntry& identity_data = (*iter).second;
Identity identity(false);
identity.load_public_key(identity_data._public_key);
identity.app_data(identity_data._app_data);
return identity;
}
else {
TRACE("Identity::recall: Unable to find identity entry for destination " + destination_hash.toHex() + ", performing destination lookup...");
Destination registered_destination(Transport::find_destination_from_hash(destination_hash));
if (registered_destination) {
TRACE("Identity::recall: Found destination " + destination_hash.toHex());
Identity identity(false);
identity.load_public_key(registered_destination.identity().get_public_key());
identity.app_data({Bytes::NONE});
return identity;
}
TRACE("Identity::recall: Unable to find destination " + destination_hash.toHex());
return {Type::NONE};
}
}
/*
Recall last heard app_data for a destination hash.
:param destination_hash: Destination hash as *bytes*.
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
*/
/*static*/ Bytes Identity::recall_app_data(const Bytes& destination_hash) {
TRACE("Identity::recall_app_data...");
auto iter = _known_destinations.find(destination_hash);
if (iter != _known_destinations.end()) {
TRACE("Identity::recall_app_data: Found identity entry for destination " + destination_hash.toHex());
const IdentityEntry& identity_data = (*iter).second;
return identity_data._app_data;
}
else {
TRACE("Identity::recall_app_data: Unable to find identity entry for destination " + destination_hash.toHex());
return {Bytes::NONE};
}
}
/*static*/ bool Identity::save_known_destinations() {
// TODO: Improve the storage method so we don't have to
// deserialize and serialize the entire table on every
// save, but the only changes. It might be possible to
// simply overwrite on exit now that every local client
// disconnect triggers a data persist.
bool success = false;
try {
if (_saving_known_destinations) {
double wait_interval = 0.2;
double wait_timeout = 5;
double wait_start = OS::time();
while (_saving_known_destinations) {
OS::sleep(wait_interval);
if (OS::time() > (wait_start + wait_timeout)) {
ERROR("Could not save known destinations to storage, waiting for previous save operation timed out.");
return false;
}
}
}
_saving_known_destinations = true;
double save_start = OS::time();
std::map<Bytes, IdentityEntry> storage_known_destinations;
// TODO
/*
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
try:
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
storage_known_destinations = umsgpack.load(file)
file.close()
except:
pass
*/
for (auto& [destination_hash, identity_entry] : storage_known_destinations) {
if (_known_destinations.find(destination_hash) == _known_destinations.end()) {
//_known_destinations[destination_hash] = storage_known_destinations[destination_hash];
//_known_destinations[destination_hash] = identity_entry;
// CBA ACCUMULATES
_known_destinations.insert({destination_hash, identity_entry});
}
}
// TODO
/*
DEBUG("Saving " + std::to_string(_known_destinations.size()) + " known destinations to storage...");
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
umsgpack.dump(Identity.known_destinations, file)
file.close()
*/
std::string time_str;
double save_time = OS::time() - save_start;
if (save_time < 1) {
time_str = std::to_string((int)(save_time*1000)) + " ms";
}
else {
time_str = std::to_string(OS::round(save_time, 1)) + " s";
}
DEBUG("Saved known destinations to storage in " + time_str);
success = true;
}
catch (std::exception& e) {
ERRORF("Error while saving known destinations to disk, the contained exception was: %s", e.what());
}
_saving_known_destinations = false;
return success;
}
/*static*/ void Identity::load_known_destinations() {
// TODO
/*
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
try:
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
loaded_known_destinations = umsgpack.load(file)
file.close()
Identity.known_destinations = {}
for known_destination in loaded_known_destinations:
if len(known_destination) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
except:
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
else:
RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
*/
}
/*static*/ void Identity::cull_known_destinations() {
TRACE("Transport::cull_path_table()");
if (_known_destinations.size() > _known_destinations_maxsize) {
// prune by age
uint16_t count = 0;
std::vector<std::pair<Bytes, IdentityEntry>> sorted_pairs;
// Copy key/value pairs from map into vector
std::for_each(_known_destinations.begin(), _known_destinations.end(), [&](const std::pair<const Bytes, IdentityEntry>& ref) {
sorted_pairs.push_back(ref);
});
// Sort vector using specified comparator
std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair<Bytes, IdentityEntry> &left, const std::pair<Bytes, IdentityEntry> &right) {
return left.second._timestamp < right.second._timestamp;
});
// Iterate vector of sorted values
for (auto& [destination_hash, identity_entry] : sorted_pairs) {
TRACE("Transport::cull_path_table: Removing destination " + destination_hash.toHex() + " from known destinations");
// Remove destination from known destinations
if (_known_destinations.erase(destination_hash) < 1) {
WARNING("Failed to remove destination " + destination_hash.toHex() + " from known destinations");
}
++count;
if (_known_destinations.size() <= _known_destinations_maxsize) {
break;
}
}
DEBUG("Removed " + std::to_string(count) + " path(s) from known destinations");
}
}
/*static*/ bool Identity::validate_announce(const Packet& packet) {
try {
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
Bytes destination_hash = packet.destination_hash();
//TRACE("Identity::validate_announce: destination_hash: " + packet.destination_hash().toHex());
Bytes public_key = packet.data().left(KEYSIZE/8);
//TRACE("Identity::validate_announce: public_key: " + public_key.toHex());
Bytes name_hash = packet.data().mid(KEYSIZE/8, NAME_HASH_LENGTH/8);
//TRACE("Identity::validate_announce: name_hash: " + name_hash.toHex());
Bytes random_hash = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8, RANDOM_HASH_LENGTH/8);
//TRACE("Identity::validate_announce: random_hash: " + random_hash.toHex());
Bytes signature = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8, SIGLENGTH/8);
//TRACE("Identity::validate_announce: signature: " + signature.toHex());
Bytes app_data;
if (packet.data().size() > (KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8)) {
app_data = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8);
}
//TRACE("Identity::validate_announce: app_data: " + app_data.toHex());
//TRACE("Identity::validate_announce: app_data text: " + app_data.toString());
Bytes signed_data;
signed_data << packet.destination_hash() << public_key << name_hash << random_hash+app_data;
//TRACE("Identity::validate_announce: signed_data: " + signed_data.toHex());
if (packet.data().size() <= KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8) {
app_data.clear();
}
Identity announced_identity(false);
announced_identity.load_public_key(public_key);
if (announced_identity.pub() && announced_identity.validate(signature, signed_data)) {
Bytes hash_material = name_hash << announced_identity.hash();
Bytes expected_hash = full_hash(hash_material).left(Type::Reticulum::TRUNCATED_HASHLENGTH/8);
//TRACE("Identity::validate_announce: destination_hash: " + packet.destination_hash().toHex());
//TRACE("Identity::validate_announce: expected_hash: " + expected_hash.toHex());
if (packet.destination_hash() == expected_hash) {
// Check if we already have a public key for this destination
// and make sure the public key is not different.
auto iter = _known_destinations.find(packet.destination_hash());
if (iter != _known_destinations.end()) {
IdentityEntry& identity_entry = (*iter).second;
if (public_key != identity_entry._public_key) {
// In reality, this should never occur, but in the odd case
// that someone manages a hash collision, we reject the announce.
CRITICAL("Received announce with valid signature and destination hash, but announced public key does not match already known public key.");
CRITICAL("This may indicate an attempt to modify network paths, or a random hash collision. The announce was rejected.");
return false;
}
}
remember(packet.get_hash(), packet.destination_hash(), public_key, app_data);
//p del announced_identity
std::string signal_str;
// TODO
/*
if packet.rssi != None or packet.snr != None:
signal_str = " ["
if packet.rssi != None:
signal_str += "RSSI "+str(packet.rssi)+"dBm"
if packet.snr != None:
signal_str += ", "
if packet.snr != None:
signal_str += "SNR "+str(packet.snr)+"dB"
signal_str += "]"
else:
signal_str = ""
*/
if (packet.transport_id()) {
TRACE("Valid announce for " + packet.destination_hash().toHex() + " " + std::to_string(packet.hops()) + " hops away, received via " + packet.transport_id().toHex() + " on " + packet.receiving_interface().toString() + signal_str);
}
else {
TRACE("Valid announce for " + packet.destination_hash().toHex() + " " + std::to_string(packet.hops()) + " hops away, received on " + packet.receiving_interface().toString() + signal_str);
}
return true;
}
else {
DEBUG("Received invalid announce for " + packet.destination_hash().toHex() + ": Destination mismatch.");
return false;
}
}
else {
DEBUG("Received invalid announce for " + packet.destination_hash().toHex() + ": Invalid signature.");
//p del announced_identity
return false;
}
}
}
catch (std::exception& e) {
ERROR("Error occurred while validating announce. The contained exception was: " + std::string(e.what()));
return false;
}
return false;
}
/*static*/ void Identity::persist_data() {
if (!Transport::reticulum() || !Transport::reticulum().is_connected_to_shared_instance()) {
save_known_destinations();
}
}
/*static*/ void Identity::exit_handler() {
persist_data();
}
/*
Encrypts information for the identity.
:param plaintext: The plaintext to be encrypted as *bytes*.
:returns: Ciphertext token as *bytes*.
:raises: *KeyError* if the instance does not hold a public key.
*/
const Bytes Identity::encrypt(const Bytes& plaintext) const {
assert(_object);
TRACE("Identity::encrypt: encrypting data...");
if (!_object->_pub) {
throw std::runtime_error("Encryption failed because identity does not hold a public key");
}
Cryptography::X25519PrivateKey::Ptr ephemeral_key = Cryptography::X25519PrivateKey::generate();
Bytes ephemeral_pub_bytes = ephemeral_key->public_key()->public_bytes();
TRACE("Identity::encrypt: ephemeral public key: " + ephemeral_pub_bytes.toHex());
// CRYPTO: create shared key for key exchange using own public key
//shared_key = ephemeral_key.exchange(self.pub)
Bytes shared_key = ephemeral_key->exchange(_object->_pub_bytes);
TRACE("Identity::encrypt: shared key: " + shared_key.toHex());
Bytes derived_key = Cryptography::hkdf(
DERIVED_KEY_LENGTH,
shared_key,
get_salt(),
get_context()
);
TRACE("Identity::encrypt: derived key: " + derived_key.toHex());
Cryptography::Token token(derived_key);
TRACE("Identity::encrypt: Token encrypting data of length " + std::to_string(plaintext.size()));
TRACE("Identity::encrypt: plaintext: " + plaintext.toHex());
Bytes ciphertext = token.encrypt(plaintext);
TRACE("Identity::encrypt: ciphertext: " + ciphertext.toHex());
return ephemeral_pub_bytes + ciphertext;
}
/*
Decrypts information for the identity.
:param ciphertext: The ciphertext to be decrypted as *bytes*.
:returns: Plaintext as *bytes*, or *None* if decryption fails.
:raises: *KeyError* if the instance does not hold a private key.
*/
const Bytes Identity::decrypt(const Bytes& ciphertext_token) const {
assert(_object);
TRACE("Identity::decrypt: decrypting data...");
if (!_object->_prv) {
throw std::runtime_error("Decryption failed because identity does not hold a private key");
}
if (ciphertext_token.size() <= Type::Identity::KEYSIZE/8/2) {
DEBUG("Decryption failed because the token size " + std::to_string(ciphertext_token.size()) + " was invalid.");
return {Bytes::NONE};
}
Bytes plaintext;
try {
//peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
Bytes peer_pub_bytes = ciphertext_token.left(Type::Identity::KEYSIZE/8/2);
//peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
//Cryptography::X25519PublicKey::Ptr peer_pub = Cryptography::X25519PublicKey::from_public_bytes(peer_pub_bytes);
TRACE("Identity::decrypt: peer public key: " + peer_pub_bytes.toHex());
// CRYPTO: create shared key for key exchange using peer public key
//shared_key = _object->_prv->exchange(peer_pub);
Bytes shared_key = _object->_prv->exchange(peer_pub_bytes);
TRACE("Identity::decrypt: shared key: " + shared_key.toHex());
Bytes derived_key = Cryptography::hkdf(
DERIVED_KEY_LENGTH,
shared_key,
get_salt(),
get_context()
);
TRACE("Identity::decrypt: derived key: " + derived_key.toHex());
Cryptography::Token token(derived_key);
//ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
Bytes ciphertext(ciphertext_token.mid(Type::Identity::KEYSIZE/8/2));
TRACE("Identity::decrypt: Token decrypting data of length " + std::to_string(ciphertext.size()));
TRACE("Identity::decrypt: ciphertext: " + ciphertext.toHex());
plaintext = token.decrypt(ciphertext);
TRACE("Identity::decrypt: plaintext: " + plaintext.toHex());
//TRACE("Identity::decrypt: Token decrypted data of length " + std::to_string(plaintext.size()));
}
catch (std::exception& e) {
DEBUG("Decryption by " + toString() + " failed: " + e.what());
}
return plaintext;
}
/*
Signs information by the identity.
:param message: The message to be signed as *bytes*.
:returns: Signature as *bytes*.
:raises: *KeyError* if the instance does not hold a private key.
*/
const Bytes Identity::sign(const Bytes& message) const {
assert(_object);
if (!_object->_sig_prv) {
throw std::runtime_error("Signing failed because identity does not hold a private key");
}
try {
return _object->_sig_prv->sign(message);
}
catch (std::exception& e) {
ERROR("The identity " + toString() + " could not sign the requested message. The contained exception was: " + e.what());
throw e;
}
}
/*
Validates the signature of a signed message.
:param signature: The signature to be validated as *bytes*.
:param message: The message to be validated as *bytes*.
:returns: True if the signature is valid, otherwise False.
:raises: *KeyError* if the instance does not hold a public key.
*/
bool Identity::validate(const Bytes& signature, const Bytes& message) const {
assert(_object);
if (_object->_pub) {
try {
TRACE("Identity::validate: Attempting to verify signature: " + signature.toHex() + " and message: " + message.toHex());
_object->_sig_pub->verify(signature, message);
return true;
}
catch (std::exception& e) {
return false;
}
}
else {
throw std::runtime_error("Signature validation failed because identity does not hold a public key");
}
}
void Identity::prove(const Packet& packet, const Destination& destination /*= {Type::NONE}*/) const {
assert(_object);
Bytes signature(sign(packet.packet_hash()));
Bytes proof_data;
if (RNS::Reticulum::should_use_implicit_proof()) {
proof_data = signature;
TRACE("Identity::prove: implicit proof data: " + proof_data.toHex());
}
else {
proof_data = packet.packet_hash() + signature;
TRACE("Identity::prove: explicit proof data: " + proof_data.toHex());
}
if (!destination) {
TRACE("Identity::prove: proving packet with proof destination...");
ProofDestination proof_destination = packet.generate_proof_destination();
Packet proof(proof_destination, packet.receiving_interface(), proof_data, Type::Packet::PROOF);
proof.send();
}
else {
TRACE("Identity::prove: proving packet with specified destination...");
Packet proof(destination, packet.receiving_interface(), proof_data, Type::Packet::PROOF);
proof.send();
}
}
void Identity::prove(const Packet& packet) const {
prove(packet, {Type::NONE});
}

200
lib/microReticulum/src/Identity.h Executable file
View File

@@ -0,0 +1,200 @@
#pragma once
#include "Log.h"
#include "Bytes.h"
#include "Type.h"
#include "Cryptography/Hashes.h"
#include "Cryptography/Ed25519.h"
#include "Cryptography/X25519.h"
#include "Cryptography/Token.h"
#include <map>
#include <string>
#include <memory>
#include <cassert>
namespace RNS {
class Destination;
class Packet;
class Identity {
private:
class IdentityEntry {
public:
IdentityEntry(double timestamp, const Bytes& packet_hash, const Bytes& public_key, const Bytes& app_data) :
_timestamp(timestamp),
_packet_hash(packet_hash),
_public_key(public_key),
_app_data(app_data)
{
}
public:
double _timestamp = 0;
Bytes _packet_hash;
Bytes _public_key;
Bytes _app_data;
};
public:
static std::map<Bytes, IdentityEntry> _known_destinations;
static bool _saving_known_destinations;
// CBA
static uint16_t _known_destinations_maxsize;
public:
Identity(bool create_keys = true);
Identity(Type::NoneConstructor none) {
MEM("Identity NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
Identity(const Identity& identity) : _object(identity._object) {
MEM("Identity object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
virtual ~Identity() {
MEM("Identity object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
inline Identity& operator = (const Identity& identity) {
_object = identity._object;
MEM("Identity object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
return *this;
}
inline operator bool() const {
return _object.get() != nullptr;
}
inline bool operator < (const Identity& identity) const {
return _object.get() < identity._object.get();
}
public:
void createKeys();
/*
:returns: The private key as *bytes*
*/
inline const Bytes get_private_key() const {
assert(_object);
return _object->_prv_bytes + _object->_sig_prv_bytes;
}
/*
:returns: The public key as *bytes*
*/
inline const Bytes get_public_key() const {
assert(_object);
return _object->_pub_bytes + _object->_sig_pub_bytes;
}
bool load_private_key(const Bytes& prv_bytes);
void load_public_key(const Bytes& pub_bytes);
inline void update_hashes() {
assert(_object);
_object->_hash = truncated_hash(get_public_key());
TRACE("Identity::update_hashes: hash: " + _object->_hash.toHex());
_object->_hexhash = _object->_hash.toHex();
};
bool load(const char* path);
bool to_file(const char* path);
inline const Bytes& get_salt() const { assert(_object); return _object->_hash; }
inline const Bytes get_context() const { return {Bytes::NONE}; }
const Bytes encrypt(const Bytes& plaintext) const;
const Bytes decrypt(const Bytes& ciphertext_token) const;
const Bytes sign(const Bytes& message) const;
bool validate(const Bytes& signature, const Bytes& message) const;
// CBA following default for reference value requires inclusiion of header
//void prove(const Packet& packet, const Destination& destination = {Type::NONE}) const;
void prove(const Packet& packet, const Destination& destination) const;
void prove(const Packet& packet) const;
static const Identity from_file(const char* path);
static void remember(const Bytes& packet_hash, const Bytes& destination_hash, const Bytes& public_key, const Bytes& app_data = {Bytes::NONE});
static Identity recall(const Bytes& destination_hash);
static Bytes recall_app_data(const Bytes& destination_hash);
static bool save_known_destinations();
static void load_known_destinations();
// CBA
static void cull_known_destinations();
/*
Get a SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: SHA-256 hash as *bytes*
*/
static inline const Bytes full_hash(const Bytes& data) {
return Cryptography::sha256(data);
}
/*
Get a truncated SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash as *bytes*
*/
static inline const Bytes truncated_hash(const Bytes& data) {
//p return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
return full_hash(data).left(Type::Identity::TRUNCATED_HASHLENGTH/8);
}
/*
Get a random SHA-256 hash.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash of random data as *bytes*
*/
static inline const Bytes get_random_hash() {
return truncated_hash(Cryptography::random(Type::Identity::TRUNCATED_HASHLENGTH/8));
}
static bool validate_announce(const Packet& packet);
static void persist_data();
static void exit_handler();
// getters/setters
inline const Bytes& encryptionPrivateKey() const { assert(_object); return _object->_prv_bytes; }
inline const Bytes& signingPrivateKey() const { assert(_object); return _object->_sig_prv_bytes; }
inline const Bytes& encryptionPublicKey() const { assert(_object); return _object->_pub_bytes; }
inline const Bytes& signingPublicKey() const { assert(_object); return _object->_sig_pub_bytes; }
inline const Bytes& hash() const { assert(_object); return _object->_hash; }
inline std::string hexhash() const { assert(_object); return _object->_hexhash; }
inline const Bytes& app_data() const { assert(_object); return _object->_app_data; }
inline void app_data(const Bytes& app_data) { assert(_object); _object->_app_data = app_data; }
inline const Cryptography::X25519PrivateKey::Ptr prv() const { assert(_object); return _object->_prv; }
inline const Cryptography::Ed25519PrivateKey::Ptr sig_prv() const { assert(_object); return _object->_sig_prv; }
inline const Cryptography::X25519PublicKey::Ptr pub() const { assert(_object); return _object->_pub; }
inline const Cryptography::Ed25519PublicKey::Ptr sig_pub() const { assert(_object); return _object->_sig_pub; }
inline std::string toString() const { if (!_object) return ""; return "{Identity:" + _object->_hash.toHex() + "}"; }
private:
class Object {
public:
Object() { MEM("Identity::Data object created, this: " + std::to_string((uintptr_t)this)); }
virtual ~Object() { MEM("Identity::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
private:
Cryptography::X25519PrivateKey::Ptr _prv;
Bytes _prv_bytes;
Cryptography::Ed25519PrivateKey::Ptr _sig_prv;
Bytes _sig_prv_bytes;
Cryptography::X25519PublicKey::Ptr _pub;
Bytes _pub_bytes;
Cryptography::Ed25519PublicKey::Ptr _sig_pub;
Bytes _sig_pub_bytes;
Bytes _hash;
std::string _hexhash;
Bytes _app_data;
friend class Identity;
};
std::shared_ptr<Object> _object;
};
}

View File

@@ -0,0 +1,104 @@
#include "Interface.h"
#include "Identity.h"
#include "Transport.h"
using namespace RNS;
using namespace RNS::Type::Interface;
/*static*/ uint8_t Interface::DISCOVER_PATHS_FOR = MODE_ACCESS_POINT | MODE_GATEWAY;
void InterfaceImpl::handle_outgoing(const Bytes& data) {
//TRACE("InterfaceImpl.handle_outgoing: data: " + data.toHex());
TRACE("InterfaceImpl.handle_outgoing");
_txb += data.size();
}
void InterfaceImpl::handle_incoming(const Bytes& data) {
//TRACE("InterfaceImpl.handle_incoming: data: " + data.toHex());
TRACE("InterfaceImpl.handle_incoming");
_rxb += data.size();
// Create temporary Interface encapsulating our own shared impl
std::shared_ptr<InterfaceImpl> self = shared_from_this();
Interface interface(self);
// Pass data on to transport for handling
Transport::inbound(data, interface);
}
void Interface::handle_incoming(const Bytes& data) {
//TRACE("Interface.handle_incoming: data: " + data.toHex());
TRACE("Interface.handle_incoming");
assert(_impl);
/*
_impl->_rxb += data.size();
// Pass data on to transport for handling
Transport::inbound(data, *this);
*/
_impl->handle_incoming(data);
}
void Interface::process_announce_queue() {
/*
if not hasattr(self, "announce_cap"):
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
if hasattr(self, "announce_queue"):
try:
now = time.time()
stale = []
for a in self.announce_queue:
if now > a["time"]+RNS.Reticulum.QUEUED_ANNOUNCE_LIFE:
stale.append(a)
for s in stale:
if s in self.announce_queue:
self.announce_queue.remove(s)
if len(self.announce_queue) > 0:
min_hops = min(entry["hops"] for entry in self.announce_queue)
entries = list(filter(lambda e: e["hops"] == min_hops, self.announce_queue))
entries.sort(key=lambda e: e["time"])
selected = entries[0]
double now = OS::time();
uint32_t wait_time = 0;
if (_impl->_bitrate > 0 && _impl->_announce_cap > 0) {
uint32_t tx_time = (len(selected["raw"])*8) / _impl->_bitrate;
wait_time = (tx_time / _impl->_announce_cap);
}
_impl->_announce_allowed_at = now + wait_time;
self.on_outgoing(selected["raw"])
if selected in self.announce_queue:
self.announce_queue.remove(selected)
if len(self.announce_queue) > 0:
timer = threading.Timer(wait_time, self.process_announce_queue)
timer.start()
except Exception as e:
self.announce_queue = []
RNS.log("Error while processing announce queue on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The announce queue for this interface has been cleared.", RNS.LOG_ERROR)
*/
}
/*
void ArduinoJson::convertFromJson(JsonVariantConst src, RNS::Interface& dst) {
TRACE(">>> Deserializing Interface");
TRACE(">>> Interface pre: " + dst.debugString());
if (!src.isNull()) {
RNS::Bytes hash;
hash.assignHex(src.as<const char*>());
TRACE(">>> Querying Transport for Interface hash " + hash.toHex());
// Query transport for matching interface
dst = Transport::find_interface_from_hash(hash);
TRACE(">>> Interface post: " + dst.debugString());
}
else {
dst = {RNS::Type::NONE};
TRACE(">>> Interface post: " + dst.debugString());
}
}
*/

View File

@@ -0,0 +1,268 @@
#pragma once
#include "Identity.h"
#include "Log.h"
#include "Bytes.h"
#include "Type.h"
#include <ArduinoJson.h>
#include <list>
#include <memory>
#include <cassert>
#include <stdint.h>
namespace RNS {
class Interface;
using HInterface = std::shared_ptr<Interface>;
class AnnounceEntry {
public:
AnnounceEntry() {}
AnnounceEntry(const Bytes& destination, double time, uint8_t hops, double emitted, const Bytes& raw) :
_destination(destination),
_time(time),
_hops(hops),
_emitted(emitted),
_raw(raw) {}
public:
Bytes _destination;
double _time = 0;
uint8_t _hops = 0;
uint64_t _emitted = 0;
Bytes _raw;
};
class InterfaceImpl : public std::enable_shared_from_this<InterfaceImpl> {
protected:
InterfaceImpl() { MEMF("InterfaceImpl object created, this: 0x%X", this); }
InterfaceImpl(const char* name) : _name(name) { MEMF("InterfaceImpl object created, this: 0x%X", this); }
public:
virtual ~InterfaceImpl() { MEMF("InterfaceImpl object destroyed, this: 0x%X", this); }
protected:
virtual bool start() { return true; }
virtual void stop() {}
virtual void loop() {}
// CBA Virtual override method for custom interface to send outgoing data
virtual void send_outgoing(const Bytes& data) = 0;
// CBA Internal method to handle housekeeping for data going out on interface
void handle_outgoing(const Bytes& data);
// CBA Internal method to handle data coming in on interface and pass on to transport
virtual void handle_incoming(const Bytes& data);
virtual const Bytes get_hash() const {
return Identity::full_hash({toString()});
}
virtual inline std::string toString() const { return "Interface[" + _name + "]"; }
protected:
Interface* _parent = nullptr;
bool _IN = false;
bool _OUT = false;
bool _FWD = false;
bool _RPT = false;
std::string _name;
size_t _rxb = 0;
size_t _txb = 0;
bool _online = false;
Bytes _ifac_identity;
Type::Interface::modes _mode = Type::Interface::MODE_NONE;
uint32_t _bitrate = 0;
uint16_t _HW_MTU = 0;
bool _AUTOCONFIGURE_MTU = false;
bool _FIXED_MTU = false;
double _announce_allowed_at = 0;
float _announce_cap = 0.0;
std::list<AnnounceEntry> _announce_queue;
bool _is_connected_to_shared_instance = false;
bool _is_local_shared_instance = false;
bool _is_backbone = false;
//Bytes _hash;
HInterface _parent_interface;
//Transport& _owner;
friend class Interface;
};
class Interface {
public:
// Which interface modes a Transport Node
// should actively discover paths for.
static uint8_t DISCOVER_PATHS_FOR;
public:
Interface(Type::NoneConstructor none) {
MEMF("Interface NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
Interface(const Interface& obj) : _impl(obj._impl) {
MEMF("Interface object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
}
Interface(std::shared_ptr<InterfaceImpl>& impl) : _impl(impl) {
MEMF("Interface object created with shared impl, this: 0x%X, impl: 0x%X", this, _impl.get());
}
Interface(InterfaceImpl* impl) : _impl(impl) {
MEMF("Interface object created with new impl, this: 0x%X, impl: 0x%X", this, _impl.get());
}
virtual ~Interface() {
MEMF("Interface object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
}
inline Interface& operator = (const Interface& obj) {
_impl = obj._impl;
MEMF("Interface object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
return *this;
}
inline Interface& operator = (InterfaceImpl* impl) {
_impl.reset(impl);
MEMF("Interface object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
return *this;
}
inline operator bool() const {
MEMF("Interface object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() != nullptr;
}
inline bool operator < (const Interface& obj) const {
MEMF("Interface object <, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() < obj._impl.get();
}
inline bool operator > (const Interface& obj) const {
MEMF("Interface object <, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() > obj._impl.get();
}
inline bool operator == (const Interface& obj) const {
MEMF("Interface object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() == obj._impl.get();
}
inline bool operator != (const Interface& obj) const {
MEMF("Interface object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
return _impl.get() != obj._impl.get();
}
inline InterfaceImpl* get() {
return _impl.get();
}
inline void clear() {
_impl.reset();
}
public:
inline bool start() { assert(_impl); return _impl->start(); }
inline void stop() { assert(_impl); return _impl->stop(); }
inline void loop() { assert(_impl); return _impl->loop(); }
inline const Bytes get_hash() const { assert(_impl); return _impl->get_hash(); }
void process_announce_queue();
// CBA ACCUMULATES
inline void add_announce(AnnounceEntry& entry) { assert(_impl); _impl->_announce_queue.push_back(entry); }
protected:
inline void send_outgoing(const Bytes& data) { assert(_impl); _impl->send_outgoing(data); }
public:
//inline void handle_incoming(const Bytes& data) { assert(_impl); _impl->handle_incoming(data); }
// Public method to handle data coming in on interface and pass on to impl
void handle_incoming(const Bytes& data);
protected:
// setters
inline void IN(bool IN) { assert(_impl); _impl->_IN = IN; }
inline void OUT(bool OUT) { assert(_impl); _impl->_OUT = OUT; }
inline void FWD(bool FWD) { assert(_impl); _impl->_FWD = FWD; }
inline void RPT(bool RPT) { assert(_impl); _impl->_RPT = RPT; }
inline void name(const char* name) { assert(_impl); _impl->_name = name; }
inline void bitrate(uint32_t bitrate) { assert(_impl); _impl->_bitrate = bitrate; }
inline void online(bool online) { assert(_impl); _impl->_online = online; }
inline void announce_allowed_at(double announce_allowed_at) { assert(_impl); _impl->_announce_allowed_at = announce_allowed_at; }
public:
// getters
inline bool IN() const { assert(_impl); return _impl->_IN; }
inline bool OUT() const { assert(_impl); return _impl->_OUT; }
inline bool FWD() const { assert(_impl); return _impl->_FWD; }
inline bool RPT() const { assert(_impl); return _impl->_RPT; }
inline bool online() const { assert(_impl); return _impl->_online; }
inline std::string name() const { assert(_impl); return _impl->_name; }
inline const Bytes& ifac_identity() const { assert(_impl); return _impl->_ifac_identity; }
inline Type::Interface::modes mode() const { assert(_impl); return _impl->_mode; }
inline void mode(Type::Interface::modes mode) { assert(_impl); _impl->_mode = mode; }
inline uint32_t bitrate() const { assert(_impl); return _impl->_bitrate; }
inline uint16_t HW_MTU() const { assert(_impl); return _impl->_HW_MTU; }
inline bool AUTOCONFIGURE_MTU() const { assert(_impl); return _impl->_AUTOCONFIGURE_MTU; }
inline bool FIXED_MTU() const { assert(_impl); return _impl->_FIXED_MTU; }
inline double announce_allowed_at() const { assert(_impl); return _impl->_announce_allowed_at; }
inline float announce_cap() const { assert(_impl); return _impl->_announce_cap; }
inline std::list<AnnounceEntry>& announce_queue() const { assert(_impl); return _impl->_announce_queue; }
inline bool is_connected_to_shared_instance() const { assert(_impl); return _impl->_is_connected_to_shared_instance; }
inline bool is_local_shared_instance() const { assert(_impl); return _impl->_is_local_shared_instance; }
inline bool is_backbone() const { assert(_impl); return _impl->_is_backbone; }
inline void is_backbone(bool val) { assert(_impl); _impl->_is_backbone = val; }
inline HInterface parent_interface() const { assert(_impl); return _impl->_parent_interface; }
virtual inline std::string toString() const { if (!_impl) return ""; return _impl->toString(); }
#ifndef NDEBUG
inline std::string debugString() const {
std::string dump;
dump = "Interface object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
return dump;
}
#endif
protected:
std::shared_ptr<InterfaceImpl> _impl;
friend class Transport;
};
}
/*
namespace ArduinoJson {
inline bool convertToJson(const RNS::Interface& src, JsonVariant dst) {
TRACE("<<< Serializing Interface");
if (!src) {
return dst.set(nullptr);
}
TRACE("<<< Interface hash " + src.get_hash().toHex());
return dst.set(src.get_hash().toHex());
}
void convertFromJson(JsonVariantConst src, RNS::Interface& dst);
inline bool canConvertFromJson(JsonVariantConst src, const RNS::Interface&) {
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
}
}
*/
/*
namespace ArduinoJson {
template <>
struct Converter<RNS::Interface> {
static bool toJson(const RNS::Interface& src, JsonVariant dst) {
if (!src) {
return dst.set(nullptr);
}
TRACE("<<< Serializing interface hash " + src.get_hash().toHex());
return dst.set(src.get_hash().toHex());
}
static RNS::Interface fromJson(JsonVariantConst src) {
if (!src.isNull()) {
RNS::Bytes hash;
hash.assignHex(src.as<const char*>());
TRACE(">>> Deserialized interface hash " + hash.toHex());
TRACE(">>> Querying transport for interface");
// Query transport for matching interface
return RNS::Interface::find_interface_from_hash(hash);
}
else {
return {RNS::Type::NONE};
}
}
static bool checkJson(JsonVariantConst src) {
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
}
};
}
*/

1836
lib/microReticulum/src/Link.cpp Executable file

File diff suppressed because it is too large Load Diff

270
lib/microReticulum/src/Link.h Executable file
View File

@@ -0,0 +1,270 @@
#pragma once
#include "Destination.h"
#include "Type.h"
#include <memory>
#include <cassert>
namespace RNS {
class ResourceRequest;
class ResourceResponse;
class RequestReceipt;
class Link;
class LinkData;
class RequestReceiptData;
class Resource;
class Packet;
class Destination;
class ResourceAdvertisement;
class PacketReceipt;
class ResourceRequest {
public:
double _requested_at = 0.0;
Bytes _path_hash;
Bytes _request_data;
};
class ResourceResponse {
Bytes request_id;
Bytes response_data;
};
/*
An instance of this class is returned by the ``request`` method of ``RNS.Link``
instances. It should never be instantiated manually. It provides methods to
check status, response time and response data when the request concludes.
*/
class RequestReceipt {
public:
class Callbacks {
public:
using response = void(*)(const RequestReceipt& packet_receipt);
using failed = void(*)(const RequestReceipt& packet_receipt);
using progress = void(*)(const RequestReceipt& packet_receipt);
public:
response _response = nullptr;
failed _failed = nullptr;
progress _progress = nullptr;
friend class RequestReceipt;
};
public:
RequestReceipt(Type::NoneConstructor none) {}
RequestReceipt(const RequestReceipt& request_receipt) : _object(request_receipt._object) {}
//RequestReceipt(const Link& link, const PacketReceipt& packet_receipt = {Type::NONE}, const Resource& resource = {Type::NONE}, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int request_size = 0);
RequestReceipt(const Link& link, const PacketReceipt& packet_receipt, const Resource& resource, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int request_size = 0);
inline RequestReceipt& operator = (const RequestReceipt& packet_receipt) {
_object = packet_receipt._object;
return *this;
}
inline operator bool() const {
return _object.get() != nullptr;
}
inline bool operator < (const RequestReceipt& packet_receipt) const {
return _object.get() < packet_receipt._object.get();
}
public:
void request_resource_concluded(const Resource& resource);
void __response_timeout_job();
void request_timed_out(const PacketReceipt& packet_receipt);
void response_resource_progress(const Resource& resource);
void response_received(const Bytes& response);
const Bytes& get_request_id() const;
Type::RequestReceipt::status get_status() const;
float get_progress() const;
const Bytes get_response() const;
double get_response_time() const;
std::string toString() const;
// getters
const Bytes& hash() const;
const Bytes& request_id() const;
size_t response_transfer_size() const;
// setters
void response_size(size_t size);
void response_transfer_size(size_t size);
private:
std::shared_ptr<RequestReceiptData> _object;
};
/*
This class is used to establish and manage links to other peers. When a
link instance is created, Reticulum will attempt to establish verified
and encrypted connectivity with the specified destination.
:param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
:param established_callback: An optional function or method with the signature *callback(link)* to be called when the link has been established.
:param closed_callback: An optional function or method with the signature *callback(link)* to be called when the link is closed.
*/
class Link {
public:
class Callbacks {
public:
using established = void(*)(Link& link);
using closed = void(*)(Link& link);
using packet = void(*)(const Bytes& plaintext, const Packet& packet);
using remote_identified = void(*)(const Link& link, const Identity& remote_identity);
using resource = void(*)(const ResourceAdvertisement& resource_advertisement);
using resource_started = void(*)(const Resource& resource);
using resource_concluded = void(*)(const Resource& resource);
public:
established _established = nullptr;
closed _closed = nullptr;
packet _packet = nullptr;
remote_identified _remote_identified = nullptr;
resource _resource = nullptr;
resource_started _resource_started = nullptr;
resource_concluded _resource_concluded = nullptr;
friend class Link;
};
public:
static uint8_t resource_strategies;
static std::set<RNS::Type::Link::link_mode> ENABLED_MODES;
static RNS::Type::Link::link_mode MODE_DEFAULT;
public:
Link(Type::NoneConstructor none) {
MEM("Link NONE object created");
}
Link(const Link& link) : _object(link._object) {
MEM("Link object copy created");
}
Link(const Destination& destination = {Type::NONE}, Callbacks::established established_callback = nullptr, Callbacks::closed closed_callback = nullptr, const Destination& owner = {Type::NONE}, const Bytes& peer_pub_bytes = {Bytes::NONE}, const Bytes& peer_sig_pub_bytes = {Bytes::NONE}, RNS::Type::Link::link_mode mode = MODE_DEFAULT);
//Link(const Destination& destination = {Type::NONE}, Callbacks::established established_callback = nullptr, Callbacks::closed closed_callback = nullptr, const Destination& owner = {Type::NONE}, const Bytes& peer_pub_bytes = {Bytes::NONE}, const Bytes& peer_sig_pub_bytes = {Bytes::NONE}, RNS::Type::Link::link_mode mode = MODE_DEFAULT);
virtual ~Link(){
MEM("Link object destroyed");
}
Link& operator = (const Link& link) {
_object = link._object;
return *this;
}
operator bool() const {
return _object.get() != nullptr;
}
bool operator < (const Link& link) const {
return _object.get() < link._object.get();
}
public:
static Bytes signalling_bytes(uint16_t mtu, RNS::Type::Link::link_mode mode);
static uint16_t mtu_from_lr_packet(const Packet& packet);
static uint16_t mtu_from_lp_packet(const Packet& packet);
static uint8_t mode_byte(RNS::Type::Link::link_mode mode);
static RNS::Type::Link::link_mode mode_from_lr_packet(const Packet& packet);
static RNS::Type::Link::link_mode mode_from_lp_packet(const Packet& packet);
static Bytes link_id_from_lr_packet(const Packet& packet);
static Link validate_request( const Destination& owner, const Bytes& data, const Packet& packet);
public:
void load_peer(const Bytes& peer_pub_bytes, const Bytes& peer_sig_pub_bytes);
void set_link_id(const Packet& packet);
void handshake();
void prove();
void prove_packet(const Packet& packet);
void validate_proof(const Packet& packet);
void identify(const Identity& identity);
const RequestReceipt request(const Bytes& path, const Bytes& data = {Bytes::NONE}, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0);
void update_mdu();
void rtt_packet(const Packet& packet);
float get_establishment_rate();
uint16_t get_mtu();
uint16_t get_mdu();
float get_expected_rate();
RNS::Type::Link::link_mode get_mode();
const Bytes& get_salt();
const Bytes get_context();
double get_age();
double no_inbound_for();
double no_outbound_for();
double no_data_for();
double inactive_for();
const Identity& get_remote_identity();
void had_outbound(bool is_keepalive = false);
void teardown();
void teardown_packet(const Packet& packet);
void link_closed();
void start_watchdog();
void __watchdog_job();
void send_keepalive();
void handle_request(const Bytes& request_id, const ResourceRequest& unpacked_request);
void handle_response(const Bytes& request_id, const Bytes& response_data, size_t response_size, size_t response_transfer_size);
void request_resource_concluded(const Resource& resource);
void response_resource_concluded(const Resource& resource);
//z const Channel& get_channel();
void receive(const Packet& packet);
const Bytes encrypt(const Bytes& plaintext);
const Bytes decrypt(const Bytes& ciphertext);
const Bytes sign(const Bytes& message);
bool validate(const Bytes& signature, const Bytes& message);
void set_link_established_callback(Callbacks::established callback);
void set_link_closed_callback(Callbacks::closed callback);
void set_packet_callback(Callbacks::packet callback);
void set_remote_identified_callback(Callbacks::remote_identified callback);
void set_resource_callback(Callbacks::resource callback);
void set_resource_started_callback(Callbacks::resource_started callback);
void set_resource_concluded_callback(Callbacks::resource_concluded callback);
void resource_concluded(const Resource& resource);
void set_resource_strategy(Type::Link::resource_strategy strategy);
void register_outgoing_resource(const Resource& resource);
void register_incoming_resource(const Resource& resource);
bool has_incoming_resource(const Resource& resource);
void cancel_outgoing_resource(const Resource& resource);
void cancel_incoming_resource(const Resource& resource);
bool ready_for_new_resource();
//void __str__();
std::string toString() const;
// getters
double rtt() const;
const Destination& destination() const;
// CBA LINK
//const Destination& link_destination() const;
const Interface& attached_interface() const;
const Bytes& link_id() const;
const Bytes& hash() const;
uint16_t mtu() const;
Type::Link::status status() const;
double establishment_timeout() const;
uint16_t establishment_cost() const;
uint8_t traffic_timeout_factor() const;
double request_time() const;
double last_inbound() const;
std::set<RequestReceipt>& pending_requests() const;
Type::Link::teardown_reason teardown_reason() const;
bool initiator() const;
// setters
void destination(const Destination& destination);
void attached_interface(const Interface& interface);
void establishment_timeout(double timeout);
void establishment_cost(uint16_t cost);
void request_time(double time);
void last_inbound(double time);
void last_outbound(double time);
void increment_tx();
void increment_txbytes(uint16_t bytes);
void status(Type::Link::status status);
void mtu(uint16_t mtu);
void mode(RNS::Type::Link::link_mode mode);
protected:
std::shared_ptr<LinkData> _object;
};
}

135
lib/microReticulum/src/LinkData.h Executable file
View File

@@ -0,0 +1,135 @@
#pragma once
//#include "LinkCallbacks.h"
#include "Link.h"
#include "Resource.h"
#include "Channel.h"
#include "Interface.h"
#include "Packet.h"
#include "Destination.h"
#include "Bytes.h"
#include "Type.h"
#include "Cryptography/Token.h"
#include <set>
namespace RNS {
class LinkData {
public:
LinkData(const Destination& destination) : _destination(destination) {
MEM("LinkData object copy created");
}
virtual ~LinkData() {
MEM("LinkData object destroyed");
}
private:
Destination _destination;
// CBA LINK
//Destination _link_destination = {Type::NONE};
Bytes _link_id;
Bytes _hash;
Type::Link::status _status = Type::Link::PENDING;
RNS::Type::Link::link_mode _mode = Link::MODE_DEFAULT;
double _rtt = 0.0;
uint16_t _mtu = RNS::Type::Reticulum::MTU;
uint16_t _mdu = 0;
uint16_t _establishment_cost = 0;
Link::Callbacks _callbacks;
Type::Link::resource_strategy _resource_strategy = Type::Link::ACCEPT_NONE;
double _last_inbound = 0.0;
double _last_outbound = 0.0;
double _last_keepalive = 0.0;
double _last_proof = 0.0;
double _last_data = 0.0;
uint16_t _tx = 0;
uint16_t _rx = 0;
uint16_t _txbytes = 0;
uint16_t _rxbytes = 0;
float _rssi = 0.0;
float _snr = 0.0;
float _q = 0.0;
uint8_t _traffic_timeout_factor = Type::Link::TRAFFIC_TIMEOUT_FACTOR;
uint16_t _keepalive_timeout_factor = Type::Link::KEEPALIVE_TIMEOUT_FACTOR;
uint16_t _keepalive = Type::Link::KEEPALIVE;
uint16_t _stale_time = Type::Link::STALE_TIME;
bool _watchdog_lock = false;
double _activated_at = 0.0;
// CBA LINK
//Type::Destination::types _type = Type::Destination::LINK;
Destination _owner = {Type::NONE};
bool _initiator = false;
uint8_t _expected_hops = 0;
Interface _attached_interface = {Type::NONE};
Identity __remote_identity = {Type::NONE};
Channel _channel = {Type::NONE};
double _establishment_timeout = 0.0;
Bytes _request_data;
Packet _packet = {Type::NONE};
double _request_time = 0.0;
float _establishment_rate = 0.0;
float _expected_rate = 0.0;
Type::Link::teardown_reason _teardown_reason = Type::Link::TEARDOWN_NONE;
Cryptography::Token::Ptr _token;
Cryptography::X25519PrivateKey::Ptr _prv;
Bytes _prv_bytes;
Cryptography::Ed25519PrivateKey::Ptr _sig_prv;
Bytes _sig_prv_bytes;
Cryptography::X25519PublicKey::Ptr _pub;
Bytes _pub_bytes;
Cryptography::Ed25519PublicKey::Ptr _sig_pub;
Bytes _sig_pub_bytes;
Cryptography::X25519PublicKey::Ptr _peer_pub;
Bytes _peer_pub_bytes;
Cryptography::Ed25519PublicKey::Ptr _peer_sig_pub;
Bytes _peer_sig_pub_bytes;
Bytes _shared_key;
Bytes _derived_key;
std::set<Resource> _incoming_resources;
std::set<Resource> _outgoing_resources;
std::set<RNS::RequestReceipt> _pending_requests;
friend class Link;
};
class RequestReceiptData {
public:
RequestReceiptData() {}
virtual ~RequestReceiptData() {}
private:
Bytes _hash;
PacketReceipt _packet_receipt = {Type::NONE};
Resource _resource = {Type::NONE};
Link _link;
double _started_at = 0.0;
Bytes _request_id;
int _request_size = 0;
Bytes _response;
size_t _response_transfer_size = 0;
size_t _response_size = 0;
Type::RequestReceipt::status _status = Type::RequestReceipt::SENT;
double _sent_at = 0.0;
int _progress = 0;
double _concluded_at = 0.0;
double _response_concluded_at = 0.0;
double _timeout = 0.0;
double _resource_response_timeout = 0.0;
double ___resource_response_timeout = 0.0;
RequestReceipt::Callbacks _callbacks;
friend class RequestReceipt;
};
}

111
lib/microReticulum/src/Log.cpp Executable file
View File

@@ -0,0 +1,111 @@
#include "Log.h"
#include "Utilities/OS.h"
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
using namespace RNS;
//LogLevel _level = LOG_VERBOSE;
LogLevel _level = LOG_TRACE;
//LogLevel _level = LOG_MEM;
RNS::log_callback _on_log = nullptr;
char _datetime[20];
const char* RNS::getLevelName(LogLevel level) {
switch (level) {
case LOG_CRITICAL:
return "!!!";
case LOG_ERROR:
return "ERR";
case LOG_WARNING:
return "WRN";
case LOG_NOTICE:
return "NOT";
case LOG_INFO:
return "INF";
case LOG_VERBOSE:
return "VRB";
case LOG_DEBUG:
return "DBG";
case LOG_TRACE:
return "---";
case LOG_MEM:
return "...";
default:
return "";
}
}
const char* RNS::getTimeString() {
#ifdef ARDUINO
uint64_t time = Utilities::OS::ltime();
if (time < 86400000) {
snprintf(_datetime, sizeof(_datetime), "%02d:%02d:%02d.%03d", (int)(time/3600000), (int)((time/60000)%60), (int)((time/1000)%60), (int)(time%1000));
}
else {
snprintf(_datetime, sizeof(_datetime), "%02d-%02d:%02d:%02d.%03d", (int)(time/86400000), (int)((time/3600000)%24), (int)((time/60000)%60), (int)((time/1000)%60), (int)(time%1000));
}
return _datetime;
#else
struct timeval tv;
gettimeofday(&tv, nullptr);
int millis = lrint(tv.tv_usec/1000.0);
if (millis >= 1000) {
millis -= 1000;
tv.tv_sec++;
}
struct tm* tm = localtime(&tv.tv_sec);
size_t len = strftime(_datetime, sizeof(_datetime), "%Y-%m-%d %H:%M:%S", tm);
snprintf(_datetime+len, sizeof(_datetime)-len, ".%03d", millis);
return _datetime;
#endif
}
void RNS::loglevel(LogLevel level) {
_level = level;
}
LogLevel RNS::loglevel() {
return _level;
}
void RNS::setLogCallback(log_callback on_log /*= nullptr*/) {
_on_log = on_log;
}
void RNS::doLog(LogLevel level, const char* msg) {
if (level > _level) {
return;
}
if (_on_log != nullptr) {
_on_log(msg, level);
return;
}
#ifdef ARDUINO
if (Serial) {
Serial.print(getTimeString());
Serial.print(" [");
Serial.print(getLevelName(level));
Serial.print("] ");
Serial.println(msg);
Serial.flush();
}
#else
printf("%s [%s] %s\n", getTimeString(), getLevelName(level), msg);
#endif
}
void HEAD(const char* msg, LogLevel level) {
if (level > _level) {
return;
}
#ifdef ARDUINO
Serial.println("");
#else
printf("\n");
#endif
doLog(level, msg);
}

124
lib/microReticulum/src/Log.h Executable file
View File

@@ -0,0 +1,124 @@
#pragma once
#ifdef ARDUINO
#include <Arduino.h>
#endif
#include <stdarg.h>
#include <string>
#define LOG(msg, level) (RNS::log(msg, level))
#define LOGF(level, msg, ...) (RNS::logf(level, msg, __VA_ARGS__))
#define HEAD(msg, level) (RNS::head(msg, level))
#define HEADF(level, msg, ...) (RNS::headf(level, msg, __VA_ARGS__))
#define CRITICAL(msg) (RNS::critical(msg))
#define CRITICALF(msg, ...) (RNS::criticalf(msg, __VA_ARGS__))
#define ERROR(msg) (RNS::error(msg))
#define ERRORF(msg, ...) (RNS::errorf(msg, __VA_ARGS__))
#define WARNING(msg) (RNS::warning(msg))
#define WARNINGF(msg, ...) (RNS::warningf(msg, __VA_ARGS__))
#define NOTICE(msg) (RNS::notice(msg))
#define NOTICEF(msg, ...) (RNS::noticef(msg, __VA_ARGS__))
#define INFO(msg) (RNS::info(msg))
#define INFOF(msg, ...) (RNS::infof(msg, __VA_ARGS__))
#define VERBOSE(msg) (RNS::verbose(msg))
#define VERBOSEF(msg, ...) (RNS::verbosef(msg, __VA_ARGS__))
#ifndef NDEBUG
#define DEBUG(msg) (RNS::debug(msg))
#define DEBUGF(msg, ...) (RNS::debugf(msg, __VA_ARGS__))
#define TRACE(msg) (RNS::trace(msg))
#define TRACEF(msg, ...) (RNS::tracef(msg, __VA_ARGS__))
#if defined(RNS_MEM_LOG)
#define MEM(msg) (RNS::mem(msg))
#define MEMF(msg, ...) (RNS::memf(msg, __VA_ARGS__))
#else
#define MEM(ignore) ((void)0)
#define MEMF(...) ((void)0)
#endif
#else
#define DEBUG(ignore) ((void)0)
#define DEBUGF(...) ((void)0)
#define TRACE(...) ((void)0)
#define TRACEF(...) ((void)0)
#define MEM(ignore) ((void)0)
#define MEMF(...) ((void)0)
#endif
namespace RNS {
enum LogLevel {
LOG_NONE = 0,
LOG_CRITICAL = 1,
LOG_ERROR = 2,
LOG_WARNING = 3,
LOG_NOTICE = 4,
LOG_INFO = 5,
LOG_VERBOSE = 6,
LOG_DEBUG = 7,
LOG_TRACE = 8,
LOG_MEM = 9
};
using log_callback = void(*)(const char* msg, LogLevel level);
const char* getLevelName(LogLevel level);
const char* getTimeString();
void loglevel(LogLevel level);
LogLevel loglevel();
void setLogCallback(log_callback on_log = nullptr);
void doLog(LogLevel level, const char* msg);
inline void doLog(LogLevel level, const char* msg, va_list vlist) { char buf[1024]; vsnprintf(buf, sizeof(buf), msg, vlist); doLog(level, buf); }
inline void log(const char* msg, LogLevel level = LOG_NOTICE) { doLog(level, msg); }
#ifdef ARDUINO
inline void log(const String& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
inline void log(const StringSumHelper& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
#endif
inline void log(const std::string& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
inline void logf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); }
void head(const char* msg, LogLevel level = LOG_NOTICE);
inline void head(const std::string& msg, LogLevel level = LOG_NOTICE) { head(msg.c_str(), level); }
inline void headf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); }
inline void critical(const char* msg) { doLog(LOG_CRITICAL, msg); }
inline void critical(const std::string& msg) { doLog(LOG_CRITICAL, msg.c_str()); }
inline void criticalf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_CRITICAL, msg, vlist); va_end(vlist); }
inline void error(const char* msg) { doLog(LOG_ERROR, msg); }
inline void error(const std::string& msg) { doLog(LOG_ERROR, msg.c_str()); }
inline void errorf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_ERROR, msg, vlist); va_end(vlist); }
inline void warning(const char* msg) { doLog(LOG_WARNING, msg); }
inline void warning(const std::string& msg) { doLog(LOG_WARNING, msg.c_str()); }
inline void warningf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_WARNING, msg, vlist); va_end(vlist); }
inline void notice(const char* msg) { doLog(LOG_NOTICE, msg); }
inline void notice(const std::string& msg) { doLog(LOG_NOTICE, msg.c_str()); }
inline void noticef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_NOTICE, msg, vlist); va_end(vlist); }
inline void info(const char* msg) { doLog(LOG_INFO, msg); }
inline void info(const std::string& msg) { doLog(LOG_INFO, msg.c_str()); }
inline void infof(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_INFO, msg, vlist); va_end(vlist); }
inline void verbose(const char* msg) { doLog(LOG_VERBOSE, msg); }
inline void verbose(const std::string& msg) { doLog(LOG_VERBOSE, msg.c_str()); }
inline void verbosef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_VERBOSE, msg, vlist); va_end(vlist); }
inline void debug(const char* msg) { doLog(LOG_DEBUG, msg); }
inline void debug(const std::string& msg) { doLog(LOG_DEBUG, msg.c_str()); }
inline void debugf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_DEBUG, msg, vlist); va_end(vlist); }
inline void trace(const char* msg) { doLog(LOG_TRACE, msg); }
inline void trace(const std::string& msg) { doLog(LOG_TRACE, msg.c_str()); }
inline void tracef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_TRACE, msg, vlist); va_end(vlist); }
inline void mem(const char* msg) { doLog(LOG_MEM, msg); }
inline void mem(const std::string& msg) { doLog(LOG_MEM, msg.c_str()); }
inline void memf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_MEM, msg, vlist); va_end(vlist); }
}

995
lib/microReticulum/src/Packet.cpp Executable file
View File

@@ -0,0 +1,995 @@
#include "Packet.h"
#include "Reticulum.h"
#include "Transport.h"
#include "Identity.h"
#include "Log.h"
#include "Utilities/OS.h"
#include <string.h>
#include <stdexcept>
using namespace RNS;
using namespace RNS::Type::PacketReceipt;
using namespace RNS::Type::Packet;
using namespace RNS::Utilities;
ProofDestination::ProofDestination(const Packet& packet) : Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::SINGLE, packet.get_hash().left(Type::Reticulum::TRUNCATED_HASHLENGTH/8))
{
}
Packet::Packet(const Destination& destination, const Interface& attached_interface, const Bytes& data, types packet_type /*= DATA*/, context_types context /*= CONTEXT_NONE*/, Type::Transport::types transport_type /*= Type::Transport::BROADCAST*/, header_types header_type /*= HEADER_1*/, const Bytes& transport_id /*= {Bytes::NONE}*/, bool create_receipt /*= true*/, context_flags context_flag /*= FLAG_UNSET*/) :
_object(new Object(destination, attached_interface))
{
if (_object->_destination) {
TRACE("Creating packet with destination...");
// CBA Should never see empty transport_type
//if (transport_type == NONE) {
// transport_type = Type::Transport::BROADCAST;
//}
// following moved to object constructor to avoid extra NONE object
//_destination = destination;
_object->_header_type = header_type;
_object->_packet_type = packet_type;
_object->_transport_type = transport_type;
_object->_context = context;
_object->_context_flag = context_flag;
_object->_transport_id = transport_id;
_object->_data = data;
if (_object->_data.size() > MDU) {
_object->_truncated = true;
_object->_data.resize(MDU);
}
_object->_flags = get_packed_flags();
_object->_create_receipt = create_receipt;
}
else {
TRACE("Creating packet without destination...");
// CBA NOTE: This variant is for creating a new packet from a received raw buffer
_object->_raw = data;
_object->_packed = true;
_object->_fromPacked = true;
_object->_create_receipt = false;
}
MEM("Packet object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
// CBA LINK
Packet::Packet(const Link& link, const Bytes& data, Type::Packet::types packet_type /*= Type::Packet::DATA*/, Type::Packet::context_types context /*= Type::Packet::CONTEXT_NONE*/, context_flags context_flag /*= FLAG_UNSET*/) :
//_object(new Object(link))
//Packet(link.destination(), data, packet_type, context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, {Bytes::NONE}, true, context_flag)
// CBA Must use a destination that targets the Link itself instead of the original destination used to create the link
Packet(Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::LINK, link.hash()), data, packet_type, context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, {Bytes::NONE}, true, context_flag)
{
TRACE("Creating packet with link...");
_object->_destination_link = link;
_object->_MTU = link.mtu();
// CBA HACK: Need to re-build packed flags since Link was assigned
_object->_flags = get_packed_flags();
MEM("Packet link object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
uint8_t Packet::get_packed_flags() {
assert(_object);
uint8_t packed_flags = 0;
if (_object->_context == LRPROOF) {
TRACE("***** Packing with LINK type");
packed_flags = (_object->_header_type << 6) | (_object->_context_flag << 5) | (_object->_transport_type << 4) | (Type::Destination::LINK << 2) | _object->_packet_type;
}
else {
if (!_object->_destination) {
throw std::logic_error("Packet destination is required");
}
TRACEF("***** Packing with %d type", _object->_destination.type());
packed_flags = (_object->_header_type << 6) | (_object->_context_flag << 5) | (_object->_transport_type << 4) | (_object->_destination.type() << 2) | _object->_packet_type;
}
return packed_flags;
}
void Packet::unpack_flags(uint8_t flags) {
assert(_object);
_object->_header_type = static_cast<header_types>((flags & 0b01000000) >> 6);
_object->_context_flag = static_cast<Type::Packet::context_flags>((flags & 0b00100000) >> 5);
_object->_transport_type = static_cast<Type::Transport::types>((flags & 0b00010000) >> 4);
_object->_destination_type = static_cast<Type::Destination::types>((flags & 0b00001100) >> 2);
_object->_packet_type = static_cast<types>(flags & 0b00000011);
}
/*
== Reticulum Wire Format ======
A Reticulum packet is composed of the following fields:
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
* The length of the Interface Access Code can vary from
1 to 64 bytes according to physical interface
capabilities and configuration.
* The ADDRESSES field contains either 1 or 2 addresses.
* Each address is 16 bytes long.
* The Header Type flag in the HEADER field determines
whether the ADDRESSES field contains 1 or 2 addresses.
* Addresses are SHA-256 hashes truncated to 16 bytes.
* The CONTEXT field is 1 byte.
* It is used by Reticulum to determine packet context.
* The DATA field is between 0 and 465 bytes.
* It contains the packets data payload.
IFAC Flag
-----------------
open 0 Packet for publically accessible interface
authenticated 1 Interface authentication is included in packet
Header Types
-----------------
type 1 0 Two byte header, one 16 byte address field
type 2 1 Two byte header, two 16 byte address fields
Propagation Types
-----------------
broadcast 00
transport 01
reserved 10
reserved 11
Destination Types
-----------------
single 00
group 01
plain 10
link 11
Packet Types
-----------------
data 00
announce 01
link request 10
proof 11
+- Packet Example -+
HEADER FIELD DESTINATION FIELDS CONTEXT FIELD DATA FIELD
_______|_______ ________________|________________ ________|______ __|_
| | | | | | | |
01010000 00000100 [HASH1, 16 bytes] [HASH2, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 4
|| | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = TRANSPORT
|+------------- Header Type = HEADER_2 (two byte header, two address fields)
+-------------- Access Codes = DISABLED
+- Packet Example -+
HEADER FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
_______|_______ _______|_______ ________|______ __|_
| | | | | | | |
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 7
|| | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST
|+------------- Header Type = HEADER_1 (two byte header, one address field)
+-------------- Access Codes = DISABLED
+- Packet Example -+
HEADER FIELD IFAC FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
_______|_______ ______|______ _______|_______ ________|______ __|_
| | | | | | | | | |
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 7
|| | | +------- Packet Type = DATA
|| | +--------- Destination Type = SINGLE
|| +----------- Propagation Type = BROADCAST
|+------------- Header Type = HEADER_1 (two byte header, one address field)
+-------------- Access Codes = ENABLED
Size examples of different packet types
---------------------------------------
The following table lists example sizes of various
packet types. The size listed are the complete on-
wire size counting all fields including headers,
but excluding any interface access codes.
- Path Request : 51 bytes
- Announce : 167 bytes
- Link Request : 83 bytes
- Link Proof : 115 bytes
- Link RTT packet : 99 bytes
- Link keepalive : 20 bytes
*/
// Reticulum Packet Structure
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |I|H|PRT|DT |PT | hops | destination... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination | context | data ... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |I|H|PRT|DT |PT | hops | destination_1... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_1... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_1... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_1... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_1 | destination_2... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_2... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_2... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_2... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ...destination_2 | context | data ... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void Packet::pack() {
assert(_object);
TRACE("Packet::pack: packing packet...");
if (!_object->_destination) {
throw std::logic_error("Packet destination is required");
}
_object->_destination_hash = _object->_destination.hash();
_object->_header.clear();
_object->_encrypted = false;
_object->_header << _object->_flags;
_object->_header << _object->_hops;
// CBA LINK
if (_object->_context == LRPROOF) {
if (!_object->_destination_link) throw std::invalid_argument("Packet is not associated with a Link");
TRACE("Packet::pack: destination link id: " + _object->_destination_link.link_id().toHex() );
_object->_header << _object->_destination_link.link_id();
_object->_ciphertext = _object->_data;
}
else {
if (_object->_header_type == HEADER_1) {
TRACE("Packet::pack: destination hash: " + _object->_destination.hash().toHex() );
_object->_header << _object->_destination.hash();
if (_object->_packet_type == ANNOUNCE) {
// Announce packets are not encrypted
_object->_ciphertext = _object->_data;
}
else if (_object->_packet_type == LINKREQUEST) {
// Link request packets are not encrypted
_object->_ciphertext = _object->_data;
}
else if (_object->_packet_type == PROOF && _object->_context == RESOURCE_PRF) {
// Resource proofs are not encrypted
_object->_ciphertext = _object->_data;
}
// CBA LINK
//p elif self.packet_type == Packet.PROOF and self.destination.type == RNS.Destination.LINK:
else if (_object->_packet_type == PROOF && _object->_destination.type() == Type::Destination::LINK) {
// Packet proofs over links are not encrypted
_object->_ciphertext = _object->_data;
}
else if (_object->_context == RESOURCE) {
// A resource takes care of encryption
// by itself
_object->_ciphertext = _object->_data;
}
else if (_object->_context == KEEPALIVE) {
// Keepalive packets contain no actual
// data
_object->_ciphertext = _object->_data;
}
else if (_object->_context == CACHE_REQUEST) {
// Cache-requests are not encrypted
_object->_ciphertext = _object->_data;
}
else {
// In all other cases, we encrypt the packet
// with the destination's encryption method
// CBA LINK
if (_object->_destination_link) {
_object->_ciphertext = _object->_destination_link.encrypt(_object->_data);
TRACEF("***** Link data: %s", _object->_ciphertext.toHex().c_str());
}
else {
_object->_ciphertext = _object->_destination.encrypt(_object->_data);
TRACEF("***** Destination Data: %s", _object->_ciphertext.toHex().c_str());
}
// CBA RATCHET
/*p TODO
if hasattr(self.destination, "latest_ratchet_id"):
self.ratchet_id = self.destination.latest_ratchet_id
*/
_object->_encrypted = true;
}
}
else if (_object->_header_type == HEADER_2) {
if (!_object->_transport_id) {
throw std::invalid_argument("Packet with header type 2 must have a transport ID");
}
TRACE("Packet::pack: transport id: " + _object->_transport_id.toHex() );
TRACE("Packet::pack: destination hash: " + _object->_destination.hash().toHex() );
_object->_header << _object->_transport_id;
_object->_header << _object->_destination.hash();
if (_object->_packet_type == ANNOUNCE) {
// Announce packets are not encrypted
_object->_ciphertext = _object->_data;
}
// CBA No default encryption here like with header type HEADER_1 ???
// CBA Is there any packet type besides ANNOUNCE with header type HEADER_2 ???
// CBA Safe to assume that all HEADER_2 type packets are in transport and therefore can not and will not be decrypted locally ???
}
}
_object->_header << (uint8_t)_object->_context;
_object->_raw = _object->_header + _object->_ciphertext;
if (_object->_raw.size() > _object->_MTU) {
throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " exceeds MTU of " + std::to_string(_object->_MTU) +" bytes");
}
_object->_packed = true;
update_hash();
}
bool Packet::unpack() {
assert(_object);
TRACE("Packet::unpack: unpacking packet...");
try {
if (_object->_raw.size() < Type::Reticulum::HEADER_MINSIZE) {
throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " does not meet minimum header size of " + std::to_string(Type::Reticulum::HEADER_MINSIZE) +" bytes");
}
const uint8_t* raw = _object->_raw.data();
// read header
_object->_flags = raw[0];
_object->_hops = raw[1];
unpack_flags(_object->_flags);
// CBA TODO detect invalid flags and throw error
if (false) {
log("Received malformed packet, dropping it.");
return false;
}
if (_object->_header_type == HEADER_2) {
if (_object->_raw.size() < Type::Reticulum::HEADER_MAXSIZE) {
throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " does not meet minimum header size of " + std::to_string(Type::Reticulum::HEADER_MAXSIZE) +" bytes");
}
_object->_transport_id.assign(raw+2, Type::Reticulum::DESTINATION_LENGTH);
_object->_destination_hash.assign(raw+Type::Reticulum::DESTINATION_LENGTH+2, Type::Reticulum::DESTINATION_LENGTH);
_object->_context = static_cast<context_types>(raw[2*Type::Reticulum::DESTINATION_LENGTH+2]);
_object->_data.assign(raw+2*Type::Reticulum::DESTINATION_LENGTH+3, _object->_raw.size()-(2*Type::Reticulum::DESTINATION_LENGTH+3));
// uknown at this point whether data is encrypted or not
_object->_encrypted = false;
}
else {
_object->_transport_id.clear();
_object->_destination_hash.assign(raw+2, Type::Reticulum::DESTINATION_LENGTH);
_object->_context = static_cast<context_types>(raw[Type::Reticulum::DESTINATION_LENGTH+2]);
_object->_data.assign(raw+Type::Reticulum::DESTINATION_LENGTH+3, _object->_raw.size()-(Type::Reticulum::DESTINATION_LENGTH+3));
// uknown at this point whether data is encrypted or not
_object->_encrypted = false;
}
_object->_packed = false;
update_hash();
}
catch (std::exception& e) {
ERROR(std::string("Received malformed packet, dropping it. The contained exception was: ") + e.what());
return false;
}
return true;
}
/*
Sends the packet.
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
*/
PacketReceipt Packet::send() {
assert(_object);
TRACE("Packet::send: sending packet...");
if (_object->_sent) {
throw std::logic_error("Packet was already sent");
}
// CBA LINK
//p if self.destination.type == RNS.Destination.LINK:
if (_object->_destination.type() == Type::Destination::LINK) {
if (!_object->_destination_link) throw std::invalid_argument("Packet is not associated with a Link");
if (_object->_destination_link.status() == Type::Link::CLOSED) {
throw std::runtime_error("Attempt to transmit over a closed link");
}
else {
_object->_destination_link.last_outbound(OS::time());
_object->_destination_link.increment_tx();
_object->_destination_link.increment_txbytes(_object->_data.size());
}
}
if (!_object->_packed) {
pack();
}
if (Transport::outbound(*this)) {
TRACE("Packet::send: successfully sent packet!!!");
//p return self.receipt
return _object->_receipt;
}
else {
ERROR("No interfaces could process the outbound packet");
_object->_sent = false;
_object->_receipt = {Type::NONE};
//p return False
return {Type::NONE};
}
}
/*
Re-sends the packet.
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
*/
bool Packet::resend() {
assert(_object);
TRACE("Packet::resend: re-sending packet...");
if (!_object->_sent) {
throw std::logic_error("Packet was not sent yet");
}
// Re-pack the packet to obtain new ciphertext for
// encrypted destinations
pack();
if (Transport::outbound(*this)) {
TRACE("Packet::resend: successfully sent packet!!!");
//z return self.receipt
// MOCK
return true;
}
else {
ERROR("No interfaces could process the outbound packet");
_object->_sent = false;
//z self.receipt = None;
return false;
}
}
void Packet::prove(const Destination& destination /*= {Type::NONE}*/) {
assert(_object);
TRACE("Packet::prove: proving packet...");
// CBA LINK
// CBA TODO: Determine under which circumstances to use _destination and which to use _link since it's unclear from this logic
//p if self.fromPacked and hasattr(self, "destination") and self.destination:
if (_object->_fromPacked && _object->_destination) {
if (_object->_destination.identity() && _object->_destination.identity().prv()) {
_object->_destination.identity().prove(*this, destination);
}
}
//p elif self.fromPacked and hasattr(self, "link") and self.link:
else if (_object->_fromPacked && _object->_link) {
_object->_link.prove_packet(*this);
}
else {
ERROR("Could not prove packet associated with neither a destination nor a link");
}
}
// Generates a special destination that allows Reticulum
// to direct the proof back to the proved packet's sender
ProofDestination Packet::generate_proof_destination() const {
return ProofDestination(*this);
}
bool Packet::validate_proof_packet(const Packet& proof_packet) {
assert(_object);
if (!_object->_receipt) {
return false;
}
return _object->_receipt.validate_proof_packet(proof_packet);
}
bool Packet::validate_proof(const Bytes& proof) {
assert(_object);
if (!_object->_receipt) {
return false;
}
return _object->_receipt.validate_proof(proof);
}
void Packet::update_hash() {
assert(_object);
_object->_packet_hash = get_hash();
}
const Bytes Packet::get_hash() const {
assert(_object);
Bytes hashable_part = get_hashable_part();
// CBA MCU SHORTER HASH
return Identity::full_hash(hashable_part);
//return Identity::truncated_hash(hashable_part);
}
const Bytes Packet::getTruncatedHash() const {
assert(_object);
Bytes hashable_part = get_hashable_part();
return Identity::truncated_hash(hashable_part);
}
const Bytes Packet::get_hashable_part() const {
assert(_object);
Bytes hashable_part;
hashable_part << (uint8_t)(_object->_raw.data()[0] & 0b00001111);
if (_object->_header_type == HEADER_2) {
//p hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
hashable_part << _object->_raw.mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
}
else {
//p hashable_part += self.raw[2:];
hashable_part << _object->_raw.mid(2);
}
return hashable_part;
}
#ifndef NDEBUG
std::string Packet::debugString() const {
if (!_object) {
return "";
}
if (_object->_packed) {
//unpack();
}
std::string str = "ph=" + _object->_packet_hash.toHex();
str += " ht=" + std::to_string(_object->_header_type);
str += " tt=" + std::to_string(_object->_transport_type);
str += " dt=" + std::to_string(_object->_destination_type);
str += " pt=" + std::to_string(_object->_packet_type);
str += " hp=" + std::to_string(_object->_hops);
str += " ti=" + _object->_transport_id.toHex();
str += " dh=" + _object->_destination_hash.toHex();
return str;
}
std::string Packet::dumpString() const {
if (!_object) {
return "";
}
//if (_object->_packed) {
// unpack();
//}
bool encrypted = true;
std::string dump;
dump = "\n------------------------------------------------------------------------------\n";
dump += "hash: " + _object->_packet_hash.toHex() + "\n";
dump += "flags: " + hexFromByte(_object->_flags) + "\n";
//dump += " header_type: " + std::to_string(_object->_header_type) + "\n";
dump += " header_type: ";
switch (_object->_header_type) {
case HEADER_1:
dump += "HEADER_1\n";
break;
case HEADER_2:
encrypted = false;
dump += "HEADER_2\n";
break;
default:
std::to_string(_object->_header_type) + "\n";
}
//dump += " transport_type: " + std::to_string(_object->_transport_type) + "\n";
dump += " transport_type: ";
switch (_object->_transport_type) {
case Type::Transport::BROADCAST:
dump += "BROADCAST\n";
break;
case Type::Transport::TRANSPORT:
dump += "TRANSPORT\n";
break;
case Type::Transport::RELAY:
dump += "RELAY\n";
break;
case Type::Transport::TUNNEL:
dump += "TUNNEL\n";
break;
case Type::Transport::NONE:
dump += "NONE\n";
break;
default:
std::to_string(_object->_transport_type) + "\n";
}
//dump += " destination_type: " + std::to_string(_object->_destination_type) + "\n";
dump += " destination_type: ";
switch (_object->_destination_type) {
case Type::Destination::SINGLE:
dump += "SINGLE\n";
break;
case Type::Destination::GROUP:
dump += "GROUP\n";
break;
case Type::Destination::PLAIN:
dump += "PLAIN\n";
break;
case Type::Destination::LINK:
dump += "LINK\n";
break;
default:
std::to_string(_object->_destination_type) + "\n";
}
//dump += " packet_type: " + std::to_string(_object->_packet_type) + "\n";
dump += " packet_type: ";
switch (_object->_packet_type) {
case DATA:
dump += "DATA\n";
break;
case ANNOUNCE:
encrypted = false;
dump += "ANNOUNCE\n";
break;
case LINKREQUEST:
encrypted = false;
dump += "LINKREQUEST\n";
break;
case PROOF:
dump += "PROOF\n";
if (_object->_context == RESOURCE_PRF) {
encrypted = false;
}
// CBA LINK
if (_object->_destination && _object->_destination.type() == Type::Destination::LINK) {
encrypted = false;
}
break;
default:
std::to_string(_object->_packet_type) + "\n";
}
dump += "hops: " + std::to_string(_object->_hops) + "\n";
dump += "transport: " + _object->_transport_id.toHex() + "\n";
dump += "destination: " + _object->_destination_hash.toHex() + "\n";
//dump += "context: " + std::to_string(_object->_context) + "\n";
dump += "context: ";
switch (_object->_context) {
case CONTEXT_NONE:
dump += "CONTEXT_NONE\n";
break;
case RESOURCE:
encrypted = false;
dump += "RESOURCE\n";
break;
case RESOURCE_ADV:
dump += "RESOURCE_ADV\n";
break;
case RESOURCE_REQ:
dump += "RESOURCE_REQ\n";
break;
case RESOURCE_HMU:
dump += "RESOURCE_HMU\n";
break;
case RESOURCE_PRF:
encrypted = false;
dump += "RESOURCE_PRF\n";
break;
case RESOURCE_ICL:
dump += "RESOURCE_ICL\n";
break;
case RESOURCE_RCL:
dump += "RESOURCE_RCL\n";
break;
case CACHE_REQUEST:
encrypted = false;
dump += "CACHE_REQUEST\n";
break;
case REQUEST:
dump += "REQUEST\n";
break;
case RESPONSE:
dump += "RESPONSE\n";
break;
case PATH_RESPONSE:
dump += "PATH_RESPONSE\n";
break;
case COMMAND:
dump += "COMMAND\n";
break;
case COMMAND_STATUS:
dump += "COMMAND_STATUS\n";
break;
case CHANNEL:
dump += "CHANNEL\n";
break;
case KEEPALIVE:
encrypted = false;
dump += "KEEPALIVE\n";
break;
case LINKIDENTIFY:
dump += "LINKIDENTIFY\n";
break;
case LINKCLOSE:
dump += "LINKCLOSE\n";
break;
case LINKPROOF:
dump += "LINKPROOF\n";
break;
case LRRTT:
dump += "LRRTT\n";
break;
case LRPROOF:
encrypted = false;
dump += "LRPROOF\n";
break;
default:
std::to_string(_object->_context) + "\n";
}
dump += "raw: " + _object->_raw.toHex() + "\n";
dump += " length: " + std::to_string(_object->_raw.size()) + "\n";
dump += "data: " + _object->_data.toHex() + "\n";
dump += " length: " + std::to_string(_object->_data.size()) + "\n";
//if ((encrypted || _object->_encrypted) && _object->_raw.size() > 0) {
if (false) {
size_t header_len = Type::Reticulum::HEADER_MINSIZE;
if (_object->_header_type == HEADER_2) {
header_len = Type::Reticulum::HEADER_MAXSIZE;
}
dump += "encrypted:\n";
dump += " header: " + _object->_raw.left(header_len).toHex() + "\n";
dump += " key: " + _object->_raw.mid(header_len, Type::Identity::KEYSIZE/8/2).toHex() + "\n";
Bytes ciphertext(_object->_raw.mid(header_len+Type::Identity::KEYSIZE/8/2));
dump += " ciphertext: " + ciphertext.toHex() + "\n";
dump += " length: " + std::to_string(ciphertext.size()) + "\n";
dump += " iv: " + ciphertext.left(16).toHex() + "\n";
dump += " sig: " + ciphertext.right(32).toHex() + "\n";
if (ciphertext.size() >= 48) {
dump += " aes ciphertext: " + ciphertext.mid(16, ciphertext.size()-48).toHex() + "\n";
dump += " length: " + std::to_string(ciphertext.size()-48) + "\n";
}
}
dump += "------------------------------------------------------------------------------\n";
return dump;
}
#endif
PacketReceipt::PacketReceipt(const Packet& packet) : _object(new Object()) {
if (!packet.destination()) {
throw std::invalid_argument("Packet with destination is required");
}
_object->_hash = packet.get_hash();
_object->_truncated_hash = packet.getTruncatedHash();
_object->_destination = packet.destination();
// CBA LINK
if (packet.destination().type() == Type::Destination::LINK) {
if (!packet.destination_link()) throw std::invalid_argument("Packet is not associated with a Link");
//p self.timeout = max(packet.destination.rtt * packet.destination.traffic_timeout_factor, RNS.Link.TRAFFIC_TIMEOUT_MIN_MS/1000)
_object->_timeout = std::max(packet.destination_link().rtt() * packet.destination_link().traffic_timeout_factor(), (double)RNS::Type::Link::TRAFFIC_TIMEOUT_MIN_MS/1000);
}
else {
//p self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
//p self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
_object->_timeout = RNS::Reticulum::get_instance().get_first_hop_timeout(_object->_destination.hash());
_object->_timeout += TIMEOUT_PER_HOP * Transport::hops_to(_object->_destination.hash());
}
}
// Validate a proof packet
bool PacketReceipt::validate_proof_packet(const Packet& proof_packet) {
if (proof_packet.link()) {
return validate_link_proof(proof_packet.data(), proof_packet.link(), proof_packet);
}
else {
return validate_proof(proof_packet.data(), proof_packet);
}
}
// Validate a raw proof for a link
//bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet /*= {Type::NONE}*/) {
bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link) {
return validate_link_proof(proof, link, {Type::NONE});
}
bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet) {
assert(_object);
TRACE("PacketReceipt::validate_link_proof: validating link proof...");
// TODO: Hardcoded as explicit proofs for now
if (true || proof.size() == EXPL_LENGTH) {
// This is an explicit proof
Bytes proof_hash = proof.left(Type::Identity::HASHLENGTH/8);
Bytes signature = proof.mid(Type::Identity::HASHLENGTH/8, Type::Identity::SIGLENGTH/8);
if (proof_hash == _object->_hash) {
//z if (link.validate(signature, _object->_hash)) {
if (false) {
_object->_status = DELIVERED;
_object->_proved = true;
_object->_concluded_at = OS::time();
//z _object->_proof_packet = proof_packet;
//z link.last_proof(_object->_concluded_at);
if (_object->_callbacks._delivery) {
try {
_object->_callbacks._delivery(*this);
}
catch (std::exception& e) {
ERROR("An error occurred while evaluating external delivery callback for " + link.toString());
ERROR("The contained exception was: " + std::string(e.what()));
}
}
return true;
}
else {
return false;
}
}
else {
return false;
}
}
else if (proof.size() == IMPL_LENGTH) {
// TODO: Why is this disabled?
// signature = proof[:RNS.Identity.SIGLENGTH//8]
// proof_valid = self.link.validate(signature, self.hash)
// if proof_valid:
// self.status = PacketReceipt.DELIVERED
// self.proved = True
// self.concluded_at = time.time()
// if self.callbacks.delivery != None:
// self.callbacks.delivery(self)
// RNS.log("valid")
// return True
// else:
// RNS.log("invalid")
// return False
}
else {
return false;
}
return false;
}
// Validate a raw proof
//bool PacketReceipt::validate_proof(const Bytes& proof, const Packet& proof_packet /*= {Type::NONE}*/) {
bool PacketReceipt::validate_proof(const Bytes& proof) {
return validate_proof(proof, {Type::NONE});
}
bool PacketReceipt::validate_proof(const Bytes& proof, const Packet& proof_packet) {
assert(_object);
TRACE("PacketReceipt::validate_proof: validating proof...");
// CBA LINK
// CBA TODO: Determine whether to use destination.identity or link.identity here!!!
if (proof.size() == EXPL_LENGTH) {
// This is an explicit proof
Bytes proof_hash = proof.left(Type::Identity::HASHLENGTH/8);
Bytes signature = proof.mid(Type::Identity::HASHLENGTH/8, Type::Identity::SIGLENGTH/8);
if (proof_hash == _object->_hash) {
if (_object->_destination.identity().validate(signature, _object->_hash)) {
_object->_status = DELIVERED;
_object->_proved = true;
_object->_concluded_at = OS::time();
//z _object->_proof_packet = proof_packet;
if (_object->_callbacks._delivery) {
try {
_object->_callbacks._delivery(*this);
}
catch (std::exception& e) {
ERROR("Error while executing proof validated callback. The contained exception was: " + std::string(e.what()));
}
}
return true;
}
else {
return false;
}
}
else {
return false;
}
}
else if (proof.size() == IMPL_LENGTH) {
// This is an implicit proof
if (!_object->_destination.identity()) {
return false;
}
Bytes signature = proof.left(Type::Identity::SIGLENGTH/8);
if (_object->_destination.identity().validate(signature, _object->_hash)) {
_object->_status = DELIVERED;
_object->_proved = true;
_object->_concluded_at = OS::time();
//z _object->_proof_packet = proof_packet;
if (_object->_callbacks._delivery) {
try {
_object->_callbacks._delivery(*this);
}
catch (std::exception& e) {
ERROR("Error while executing proof validated callback. The contained exception was: " + std::string(e.what()));
}
}
return true;
}
else {
return false;
}
}
else {
return false;
}
return false;
}
void PacketReceipt::check_timeout() {
assert(_object);
if (_object->_status == SENT && is_timed_out()) {
if (_object->_timeout == -1) {
_object->_status = CULLED;
}
else {
_object->_status = FAILED;
}
_object->_concluded_at = Utilities::OS::time();
if (_object->_callbacks._timeout) {
//z thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
//z thread.daemon = True
//z thread.start();
}
}
}
/*
void ArduinoJson::convertFromJson(JsonVariantConst src, RNS::Packet& dst) {
if (!src.isNull()) {
RNS::Bytes hash;
hash.assignHex(src.as<const char*>());
// Query transport for matching interface
dst = Transport::get_cached_packet(hash);
}
else {
dst = {RNS::Type::NONE};
}
}
*/

382
lib/microReticulum/src/Packet.h Executable file
View File

@@ -0,0 +1,382 @@
#pragma once
#include "Link.h"
#include "Interface.h"
#include "Destination.h"
#include "Bytes.h"
#include "Log.h"
#include "Type.h"
#include "Utilities/OS.h"
#include <memory>
#include <cassert>
#include <stdint.h>
#include <time.h>
namespace RNS {
class ProofDestination;
class PacketReceipt;
class Packet;
class ProofDestination : public Destination {
public:
ProofDestination(const Packet& packet);
// CBA Can't use virtual methods because they are lost in object copies
//inline virtual const Bytes encrypt(const Bytes& data) {
// return data;
//}
};
/*
The PacketReceipt class is used to receive notifications about
:ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
of this class are never created manually, but always returned from
the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
*/
class PacketReceipt {
public:
class Callbacks {
public:
using delivery = void(*)(const PacketReceipt& packet_receipt);
using timeout = void(*)(const PacketReceipt& packet_receipt);
public:
delivery _delivery = nullptr;
timeout _timeout = nullptr;
friend class PacketReceipt;
};
public:
PacketReceipt() : _object(new Object()) {}
PacketReceipt(Type::NoneConstructor none) {}
PacketReceipt(const PacketReceipt& packet_receipt) : _object(packet_receipt._object) {}
PacketReceipt(const Packet& packet);
inline PacketReceipt& operator = (const PacketReceipt& packet_receipt) {
_object = packet_receipt._object;
return *this;
}
inline operator bool() const {
return _object.get() != nullptr;
}
inline bool operator < (const PacketReceipt& packet_receipt) const {
return _object.get() < packet_receipt._object.get();
}
public:
bool validate_proof_packet(const Packet& proof_packet);
//bool validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet = {Type::NONE});
bool validate_link_proof(const Bytes& proof, const Link& link);
bool validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet);
//bool validate_proof(const Bytes& proof, const Packet& proof_packet = {Type::NONE});
bool validate_proof(const Bytes& proof);
bool validate_proof(const Bytes& proof, const Packet& proof_packet);
inline double get_rtt() { assert(_object); return _object->_concluded_at - _object->_sent_at; }
inline bool is_timed_out() { assert(_object); return ((_object->_sent_at + _object->_timeout) < Utilities::OS::time()); }
void check_timeout();
// :param timeout: The timeout in seconds.
inline void set_timeout(int16_t timeout) { assert(_object); _object->_timeout = timeout; }
/*
Sets a function that gets called if a successfull delivery has been proven.
:param callback: A *callable* with the signature *callback(packet_receipt)*
*/
inline void set_delivery_callback(Callbacks::delivery callback) {
assert(_object);
_object->_callbacks._delivery = callback;
}
/*
Sets a function that gets called if the delivery times out.
:param callback: A *callable* with the signature *callback(packet_receipt)*
*/
inline void set_timeout_callback(Callbacks::timeout callback) {
assert(_object);
_object->_callbacks._timeout = callback;
}
// getters
inline const Bytes& hash() const { assert(_object); return _object->_hash; }
inline Type::PacketReceipt::Status status() const { assert(_object); return _object->_status; }
inline bool proved() const { assert(_object); return _object->_proved; }
inline double concluded_at() const { assert(_object); return _object->_concluded_at; }
inline const Bytes& truncated_hash() const { assert(_object); return _object->_truncated_hash; }
inline const Callbacks& callbacks() const { assert(_object); return _object->_callbacks; }
// setters
inline void status(Type::PacketReceipt::Status status) { assert(_object); _object->_status = status; }
inline void proved(bool proved) { assert(_object); _object->_proved = proved; }
inline void concluded_at(double concluded_at) { assert(_object); _object->_concluded_at = concluded_at; }
private:
class Object {
public:
Object() {}
virtual ~Object() {}
private:
Bytes _hash;
Bytes _truncated_hash;
bool _sent = true;
double _sent_at = Utilities::OS::time();
bool _proved = false;
Type::PacketReceipt::Status _status = Type::PacketReceipt::SENT;
Destination _destination = {Type::NONE};
Callbacks _callbacks;
double _concluded_at = 0;
// CBA TODO This shoujld almost certainly not be a reference but we have an issue with circular dependency between Packet and PacketReceipt
//Packet _proof_packet = {Type::NONE};
int16_t _timeout = 0;
friend class PacketReceipt;
};
std::shared_ptr<Object> _object;
};
class Packet {
public:
//static constexpr const uint8_t EMPTY_DESTINATION[Type::Reticulum::DESTINATION_LENGTH] = {0};
uint8_t EMPTY_DESTINATION[Type::Reticulum::DESTINATION_LENGTH] = {0};
public:
Packet(Type::NoneConstructor none) {
MEM("Packet NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
Packet(const Packet& packet) : _object(packet._object) {
MEM("Packet object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
Packet(
const Destination& destination,
const Interface& attached_interface,
const Bytes& data,
Type::Packet::types packet_type = Type::Packet::DATA,
Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
Type::Transport::types transport_type = Type::Transport::BROADCAST,
Type::Packet::header_types header_type = Type::Packet::HEADER_1,
const Bytes& transport_id = {Bytes::NONE},
bool create_receipt = true,
Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
);
Packet(
const Destination& destination,
const Bytes& data,
Type::Packet::types packet_type = Type::Packet::DATA,
Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
Type::Transport::types transport_type = Type::Transport::BROADCAST,
Type::Packet::header_types header_type = Type::Packet::HEADER_1,
const Bytes& transport_id = {Bytes::NONE},
bool create_receipt = true,
Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
) : Packet(destination, {Type::NONE}, data, packet_type, context, transport_type, header_type, transport_id, create_receipt, context_flag) {}
// CBA LINK
Packet(
const Link& link,
const Bytes& data,
Type::Packet::types packet_type = Type::Packet::DATA,
Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
);
virtual ~Packet() {
MEM("Packet object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
inline Packet& operator = (const Packet& packet) {
_object = packet._object;
MEM("Packet object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
return *this;
}
inline operator bool() const {
return _object.get() != nullptr;
}
inline bool operator < (const Packet& packet) const {
return _object.get() < packet._object.get();
}
private:
/*
void setTransportId(const uint8_t* transport_id);
void setHeader(const uint8_t* header);
void setRaw(const uint8_t* raw, uint16_t len);
void setData(const uint8_t* rata, uint16_t len);
*/
public:
uint8_t get_packed_flags();
void unpack_flags(uint8_t flags);
void pack();
bool unpack();
PacketReceipt send();
bool resend();
void prove(const Destination& destination = {Type::NONE});
ProofDestination generate_proof_destination() const;
bool validate_proof_packet(const Packet& proof_packet);
bool validate_proof(const Bytes& proof);
void update_hash();
const Bytes get_hash() const;
const Bytes getTruncatedHash() const;
const Bytes get_hashable_part() const;
inline std::string toString() const { if (!_object) return ""; return "{Packet:" + _object->_packet_hash.toHex() + "}"; }
// getters
inline const Destination& destination() const { assert(_object); return _object->_destination; }
inline const Link& link() const { assert(_object); return _object->_link; }
inline const Interface& attached_interface() const { assert(_object); return _object->_attached_interface; }
inline const Interface& receiving_interface() const { assert(_object); return _object->_receiving_interface; }
inline Type::Packet::header_types header_type() const { assert(_object); return _object->_header_type; }
inline Type::Packet::context_flags context_flag() const { assert(_object); return _object->_context_flag; }
inline Type::Transport::types transport_type() const { assert(_object); return _object->_transport_type; }
inline Type::Destination::types destination_type() const { assert(_object); return _object->_destination_type; }
inline Type::Packet::types packet_type() const { assert(_object); return _object->_packet_type; }
inline Type::Packet::context_types context() const { assert(_object); return _object->_context; }
inline bool sent() const { assert(_object); return _object->_sent; }
inline double sent_at() const { assert(_object); return _object->_sent_at; }
inline bool create_receipt() const { assert(_object); return _object->_create_receipt; }
inline const PacketReceipt& receipt() const { assert(_object); return _object->_receipt; }
inline uint8_t flags() const { assert(_object); return _object->_flags; }
inline uint8_t hops() const { assert(_object); return _object->_hops; }
inline bool cached() const { assert(_object); return _object->_cached; }
inline const Bytes& packet_hash() const { assert(_object); return _object->_packet_hash; }
inline const Bytes& destination_hash() const { assert(_object); return _object->_destination_hash; }
inline const Bytes& transport_id() const { assert(_object); return _object->_transport_id; }
inline const Bytes& raw() const { assert(_object); return _object->_raw; }
inline const Bytes& data() const { assert(_object); return _object->_data; }
// CBA LINK
inline const Link& destination_link() const { assert(_object); return _object->_destination_link; }
//CBA Following method is only used by Resource to access decrypted resource advertisement form Link. Consider a better way.
inline const Bytes& plaintext() { assert(_object); return _object->_plaintext; }
// setters
inline void destination(const Destination& destination) { assert(_object); _object->_destination = destination; }
inline void link(const Link& link) { assert(_object); _object->_link = link; }
inline void receiving_interface(const Interface& receiving_interface) { assert(_object); _object->_receiving_interface = receiving_interface; }
inline void sent(bool sent) { assert(_object); _object->_sent = sent; }
inline void sent_at(double sent_at) { assert(_object); _object->_sent_at = sent_at; }
inline void receipt(const PacketReceipt& receipt) { assert(_object); _object->_receipt = receipt; }
inline void hops(uint8_t hops) { assert(_object); _object->_hops = hops; }
inline void cached(bool cached) { assert(_object); _object->_cached = cached; }
inline void transport_id(const Bytes& transport_id) { assert(_object); _object->_transport_id = transport_id; }
//CBA Following method is only used by Link to provide Resource access to decrypted resource advertisement. Consider a better way.
inline void plaintext(const Bytes& plaintext) { assert(_object); _object->_plaintext = plaintext; }
#ifndef NDEBUG
std::string debugString() const;
std::string dumpString() const;
#endif
private:
class Object {
public:
Object(const Destination& destination, const Interface& attached_interface) : _destination(destination), _attached_interface(attached_interface) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
// CBA LINK
//Object(const Destination& destination, const Link& destination_link) : _destination(destination), _destination_link(destination_link) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
//Object(const Link& link) : _destination(link.destination()), _destination_link(link) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
virtual ~Object() { MEM("Packet::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
private:
Destination _destination = {Type::NONE};
// CBA LINK
// CBA TODO: Determine if _link (assigned late by Transport) and _destination_link (assigned in constructor) can be one and the same !!!
Link _destination_link = {Type::NONE};
Link _link = {Type::NONE};
Interface _attached_interface = {Type::NONE};
Interface _receiving_interface = {Type::NONE};
Type::Packet::header_types _header_type = Type::Packet::HEADER_1;
Type::Transport::types _transport_type = Type::Transport::BROADCAST;
Type::Destination::types _destination_type = Type::Destination::SINGLE;
Type::Packet::types _packet_type = Type::Packet::DATA;
Type::Packet::context_types _context = Type::Packet::CONTEXT_NONE;
Type::Packet::context_flags _context_flag = Type::Packet::FLAG_UNSET;
uint8_t _flags = 0;
uint8_t _hops = 0;
bool _packed = false;
bool _sent = false;
bool _create_receipt = false;
bool _fromPacked = false;
bool _truncated = false; // whether data was truncated
bool _encrypted = false; // whether data is encrypted
bool _cached = false; // whether packet has been cached
PacketReceipt _receipt = {Type::NONE};
uint16_t _MTU = Type::Reticulum::MTU;
double _sent_at = 0;
float _rssi = 0.0;
float _snr = 0.0;
float _q = 0.0;
Bytes _packet_hash;
Bytes _ratchet_id;
Bytes _destination_hash;
Bytes _transport_id;
Bytes _raw; // header + ( plaintext | ciphertext-token )
Bytes _data; // plaintext | ciphertext
Bytes _plaintext; // used exclusively to relay decrypted resource advertisement form Link to Resource
Bytes _header;
Bytes _ciphertext;
friend class Packet;
};
std::shared_ptr<Object> _object;
};
}
/*
namespace ArduinoJson {
inline bool convertToJson(const RNS::Packet& src, JsonVariant dst) {
if (!src) {
return dst.set(nullptr);
}
return dst.set(src.get_hash().toHex());
}
void convertFromJson(JsonVariantConst src, RNS::Packet& dst);
inline bool canConvertFromJson(JsonVariantConst src, const RNS::Packet&) {
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
}
}
*/
/*
namespace ArduinoJson {
template <>
struct Converter<RNS::Packet> {
static bool toJson(const RNS::Packet& src, JsonVariant dst) {
if (!src) {
return dst.set(nullptr);
}
TRACE("<<< Serializing packet hash " + src.get_hash().toHex());
return dst.set(src.get_hash().toHex());
}
static RNS::Packet fromJson(JsonVariantConst src) {
if (!src.isNull()) {
RNS::Bytes hash;
hash.assignHex(src.as<const char*>());
TRACE(">>> Deserialized packet hash " + hash.toHex());
TRACE(">>> Querying transport for cached packet");
// Query transport for matching interface
return RNS::Packet::get_cached_packet(hash);
}
else {
return {RNS::Type::NONE};
}
}
static bool checkJson(JsonVariantConst src) {
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
}
};
}
*/

View File

@@ -0,0 +1,122 @@
#include "Resource.h"
#include "ResourceData.h"
#include "Reticulum.h"
#include "Transport.h"
#include "Packet.h"
#include "Log.h"
#include <algorithm>
using namespace RNS;
using namespace RNS::Type::Resource;
using namespace RNS::Utilities;
//Resource::Resource(const Link& link /*= {Type::NONE}*/) :
// _object(new ResourceData(link))
//{
// assert(_object);
// MEM("Resource object created");
//}
Resource::Resource(const Bytes& data, const Link& link, const Bytes& request_id, bool is_response, double timeout) :
_object(new ResourceData(link))
{
assert(_object);
MEM("Resource object created");
}
Resource::Resource(const Bytes& data, const Link& link, bool advertise /*= true*/, bool auto_compress /*= true*/, Callbacks::concluded callback /*= nullptr*/, Callbacks::progress progress_callback /*= nullptr*/, double timeout /*= 0.0*/, int segment_index /*= 1*/, const Bytes& original_hash /*= {Type::NONE}*/, const Bytes& request_id /*= {Type::NONE}*/, bool is_response /*= false*/) :
_object(new ResourceData(link))
{
assert(_object);
MEM("Resource object created");
}
void Resource::validate_proof(const Bytes& proof_data) {
}
void Resource::cancel() {
}
/*
:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
*/
float Resource::get_progress() const {
/*
assert(_object);
if (_object->_initiator) {
_object->_processed_parts = (_object->_segment_index-1)*math.ceil(Type::Resource::MAX_EFFICIENT_SIZE/Type::Resource::SDU);
_object->_processed_parts += _object->sent_parts;
_object->_progress_total_parts = float(_object->grand_total_parts);
}
else {
_object->_processed_parts = (_object->_segment_index-1)*math.ceil(Type::Resource::MAX_EFFICIENT_SIZE/Type::Resource::SDU);
_object->_processed_parts += _object->_received_count;
if (_object->split) {
_object->progress_total_parts = float(math.ceil(_object->total_size/Type::Resource::SDU));
}
else {
_object->progress_total_parts = float(_object->total_parts);
}
}
return (float)_object->processed_parts / (float)_object->progress_total_parts;
*/
return 0.0;
}
void Resource::set_concluded_callback(Callbacks::concluded callback) {
assert(_object);
_object->_callbacks._concluded = callback;
}
void Resource::set_progress_callback(Callbacks::progress callback) {
assert(_object);
_object->_callbacks._progress = callback;
}
std::string Resource::toString() const {
if (!_object) {
return "";
}
//return "<"+RNS.hexrep(self.hash,delimit=False)+"/"+RNS.hexrep(self.link.link_id,delimit=False)+">"
//return "{Resource:" + _object->_hash.toHex() + "}";
return "{Resource: unknown}";
}
// getters
const Bytes& Resource::hash() const {
assert(_object);
return _object->_hash;
}
const Bytes& Resource::request_id() const {
assert(_object);
return _object->_request_id;
}
const Bytes& Resource::data() const {
assert(_object);
return _object->_data;
}
const Type::Resource::status Resource::status() const {
assert(_object);
return _object->_status;
}
const size_t Resource::size() const {
assert(_object);
return _object->_size;
}
const size_t Resource::total_size() const {
assert(_object);
return _object->_total_size;
}
// setters

110
lib/microReticulum/src/Resource.h Executable file
View File

@@ -0,0 +1,110 @@
#pragma once
#include "Destination.h"
#include "Type.h"
#include <memory>
#include <cassert>
namespace RNS {
class ResourceData;
class Packet;
class Destination;
class Link;
class Resource;
class Resource {
public:
class Callbacks {
public:
// CBA std::function apparently not implemented in NRF52 framework
//typedef std::function<void(const Resource& resource)> concluded;
using concluded = void(*)(const Resource& resource);
using progress = void(*)(const Resource& resource);
public:
concluded _concluded = nullptr;
progress _progress = nullptr;
friend class Resource;
};
public:
Resource(Type::NoneConstructor none) {
MEM("Resource NONE object created");
}
Resource(const Resource& resource) : _object(resource._object) {
MEM("Resource object copy created");
}
//Resource(const Link& link = {Type::NONE});
Resource(const Bytes& data, const Link& link, const Bytes& request_id, bool is_response, double timeout);
Resource(const Bytes& data, const Link& link, bool advertise = true, bool auto_compress = true, Callbacks::concluded callback = nullptr, Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int segment_index = 1, const Bytes& original_hash = {Type::NONE}, const Bytes& request_id = {Type::NONE}, bool is_response = false);
virtual ~Resource(){
MEM("Resource object destroyed");
}
Resource& operator = (const Resource& resource) {
_object = resource._object;
return *this;
}
operator bool() const {
return _object.get() != nullptr;
}
bool operator < (const Resource& resource) const {
return _object.get() < resource._object.get();
//return _object->_hash < resource._object->_hash;
}
public:
//p static def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
public:
//p def hashmap_update_packet(self, plaintext):
//p def hashmap_update(self, segment, hashmap):
//p def get_map_hash(self, data):
//p def advertise(self):
//p def __advertise_job(self):
//p def watchdog_job(self):
//p def __watchdog_job(self):
//p def assemble(self):
//p def prove(self):
void validate_proof(const Bytes& proof_data);
//p def receive_part(self, packet):
//p def request_next(self):
//p def request(self, request_data):
void cancel();
//p def set_callback(self, callback):
//p def progress_callback(self, callback):
float get_progress() const;
//p def get_transfer_size(self):
//p def get_data_size(self):
//p def get_parts(self):
//p def get_segments(self):
//p def get_hash(self):
//p def is_compressed(self):
void set_concluded_callback(Callbacks::concluded callback);
void set_progress_callback(Callbacks::progress callback);
std::string toString() const;
// getters
const Bytes& hash() const;
const Bytes& request_id() const;
const Bytes& data() const;
const Type::Resource::status status() const;
const size_t size() const;
const size_t total_size() const;
// setters
protected:
std::shared_ptr<ResourceData> _object;
};
class ResourceAdvertisement {
};
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Resource.h"
#include "Interface.h"
#include "Packet.h"
#include "Destination.h"
#include "Bytes.h"
#include "Type.h"
#include "Cryptography/Fernet.h"
namespace RNS {
class ResourceData {
public:
ResourceData(const Link& link) : _link(link) {}
virtual ~ResourceData() {}
private:
Link _link;
Bytes _hash;
Bytes _request_id;
Bytes _data;
Type::Resource::status _status = Type::Resource::NONE;
size_t _size = 0;
size_t _total_size = 0;
Resource::Callbacks _callbacks;
friend class Resource;
};
}

View File

@@ -0,0 +1,492 @@
#include "Reticulum.h"
#include "Transport.h"
#include "Log.h"
//#include <TransistorNoiseSource.h>
#include <RNG.h>
#ifdef ARDUINO
#include <Arduino.h>
//#include <TransistorNoiseSource.h>
#endif
using namespace RNS;
using namespace RNS::Type::Reticulum;
using namespace RNS::Utilities;
/*static*/ //std::string Reticulum::_storagepath;
/*static*/ char Reticulum::_storagepath[FILEPATH_MAXSIZE];
/*static*/ //std::string Reticulum::_cachepath;
/*static*/ char Reticulum::_cachepath[FILEPATH_MAXSIZE];
/*static*/ const Reticulum& Reticulum::_instance = {Type::NONE};
/*static*/ bool Reticulum::__transport_enabled = false;
/*static*/ bool Reticulum::__link_mtu_discovery = RNS::Type::Reticulum::LINK_MTU_DISCOVERY;
/*static*/ bool Reticulum::__remote_management_enabled = false;
/*static*/ bool Reticulum::__use_implicit_proof = true;
/*static*/ bool Reticulum::__allow_probes = false;
/*static*/ bool Reticulum::panic_on_interface_error = false;
#ifdef ARDUINO
// Noise source to seed the random number generator.
//TransistorNoiseSource noise(A1);
#endif
/*
Initialises and starts a Reticulum instance. This must be
done before any other operations, and Reticulum will not
pass any traffic before being instantiated.
:param configdir: Full path to a Reticulum configuration directory.
*/
/*p TODO
@staticmethod
void Reticulum::exit_handler():
# This exit handler is called whenever Reticulum is asked to
# shut down, and will in turn call exit handlers in other
# classes, saving necessary information to disk and carrying
# out cleanup operations.
Transport::exit_handler()
RNS.Identity.exit_handler()
@staticmethod
void Reticulum::sigint_handler(signal, frame):
Transport::detach_interfaces()
RNS.exit()
@staticmethod
void Reticulum::sigterm_handler(signal, frame):
Transport::detach_interfaces()
RNS.exit()
*/
//def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
Reticulum::Reticulum() : _object(new Object()) {
MEM("Reticulum default object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
// Initialize random number generator
TRACE("Initializing RNG...");
RNG.begin("Reticulum");
TRACE("RNG initial random value: " + std::to_string(Cryptography::randomnum()));
#ifdef ARDUINO
// Add a noise source to the list of sources known to RNG.
//RNG.addNoiseSource(noise);
#endif
//z RNS.vendor.platformutils.platform_checks()
/*p TODO
if configdir != None:
Reticulum.configdir = configdir
else:
if os.path.isdir("/etc/reticulum") and os.path.isfile("/etc/reticulum/config"):
Reticulum.configdir = "/etc/reticulum"
elif os.path.isdir(Reticulum.userdir+"/.config/reticulum") and os.path.isfile(Reticulum.userdir+"/.config/reticulum/config"):
Reticulum.configdir = Reticulum.userdir+"/.config/reticulum"
else:
Reticulum.configdir = Reticulum.userdir+"/.reticulum"
if logdest == RNS.LOG_FILE:
RNS.logdest = RNS.LOG_FILE
RNS.logfile = Reticulum.configdir+"/logfile"
Reticulum.configpath = Reticulum.configdir+"/config"
Reticulum.storagepath = Reticulum.configdir+"/storage"
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
*/
// CBA TEST
#ifdef ARDUINO
//_storagepath = "";
strncpy(_storagepath, "", FILEPATH_MAXSIZE);
//_cachepath = "/cache";
strncpy(_cachepath, "/cache", FILEPATH_MAXSIZE);
#else
//_storagepath = ".";
strncpy(_storagepath, ".", FILEPATH_MAXSIZE);
//_cachepath = "./cache";
strncpy(_cachepath, "./cache", FILEPATH_MAXSIZE);
#endif
#ifdef ARDUINO
#if defined(RNS_USE_FS)
// load time offset from file if it exists
try {
char time_offset_path[FILEPATH_MAXSIZE];
snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
if (OS::file_exists(time_offset_path)) {
Bytes buf;
if (OS::read_file(time_offset_path, buf) == 8) {
uint64_t offset = *(uint64_t*)buf.data();
DEBUG("Read time offset of " + std::to_string(offset) + " from file");
OS::setTimeOffset(offset);
}
}
}
catch (std::exception& e) {
ERRORF("Failed to load time offset, the contained exception was: %s", e.what());
}
#endif
#endif
// Initialize time-based variables *after* time offset update
_object->_last_data_persist = OS::time();
_object->_last_cache_clean = 0.0;
_object->_jobs_last_run = OS::time();
/*p TODO
if not os.path.isdir(Reticulum.storagepath):
os.makedirs(Reticulum.storagepath)
if not os.path.isdir(Reticulum.cachepath):
os.makedirs(Reticulum.cachepath)
if not os.path.isdir(Reticulum.resourcepath):
os.makedirs(Reticulum.resourcepath)
if not os.path.isdir(Reticulum.identitypath):
os.makedirs(Reticulum.identitypath)
if os.path.isfile(self.configpath):
try:
self.config = ConfigObj(self.configpath)
except Exception as e:
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
RNS.panic()
else:
RNS.log("Could not load config file, creating default configuration file...")
self.__create_default_config()
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
time.sleep(1.5)
self.__apply_config()
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
RNS.Identity.load_known_destinations()
*/
// CBA Moved to start() so Transport is not started until after interfaces are setup
//Transport::start(*this);
/*p TODO
self.rpc_addr = ("127.0.0.1", self.local_control_port)
self.rpc_key = RNS.Identity.full_hash(Transport::identity.get_private_key())
if self.is_shared_instance:
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key)
thread = threading.Thread(target=self.rpc_loop)
thread.daemon = True
thread.start()
atexit.register(Reticulum.exit_handler)
signal.signal(signal.SIGINT, Reticulum.sigint_handler)
signal.signal(signal.SIGTERM, Reticulum.sigterm_handler)
*/
MEM("Reticulum default object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
void Reticulum::start() {
INFO("Total memory: " + std::to_string(OS::heap_size()));
INFO("Total flash: " + std::to_string(OS::storage_size()));
INFO("Starting Transport...");
Transport::start(*this);
}
void Reticulum::loop() {
assert(_object);
if (!_object->_is_connected_to_shared_instance) {
// Perform Reticulum housekeeping
if (OS::time() > (_object->_jobs_last_run + JOB_INTERVAL)) {
jobs();
_object->_jobs_last_run = OS::time();
}
// Perform interface processing
for (auto& [hash, interface] : Transport::get_interfaces()) {
interface.loop();
}
// Perform Filesystem processing
FileSystem& filesystem = OS::get_filesystem();
if (filesystem) {
filesystem.loop();
}
// Perform Transport processing
RNS::Transport::loop();
}
// Perform random number gnerator housekeeping
RNG.loop();
}
void Reticulum::jobs() {
double now = OS::time();
#if 1
// CBA Detect low-memory condition and reset
if (OS::heap_size() > 0) {
uint8_t remaining = (uint8_t)((double)OS::heap_available() / (double)OS::heap_size() * 100.0);
if (remaining <= 2) {
head("DETECTED LOW-MEMORY CONDITION (" + std::to_string(remaining) + "%), RESETTING!!!", LOG_CRITICAL);
persist_data();
#if defined(ESP32)
ESP.restart();
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
//dbgDumpMemory();
NVIC_SystemReset();
#endif
}
}
#endif
if (now > _object->_last_cache_clean + CLEAN_INTERVAL) {
clean_caches();
_object->_last_cache_clean = OS::time();
}
if (now > _object->_last_data_persist + PERSIST_INTERVAL) {
persist_data();
}
}
// CBA TODO
/*
void Reticulum::start_local_interface() {
}
void Reticulum::apply_config() {
}
void Reticulum::_add_interface(self,interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
*/
void Reticulum::should_persist_data() {
if (OS::time() > _object->_last_data_persist + GRACIOUS_PERSIST_INTERVAL) {
persist_data();
}
}
void Reticulum::persist_data() {
TRACE("Persisting transport and identity data...");
Transport::persist_data();
Identity::persist_data();
#ifdef ARDUINO
#if defined(RNS_USE_FS)
// write time offset to file
try {
char time_offset_path[FILEPATH_MAXSIZE];
snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
uint64_t offset = OS::ltime();
DEBUGF("Writing time offset of %llu to file %s", offset, time_offset_path);
Bytes buf((uint8_t*)&offset, sizeof(offset));
OS::write_file(time_offset_path, buf);
}
catch (std::exception& e) {
ERRORF("Failed to write time offset, the contained exception was: %s", e.what());
}
#endif
#endif
_object->_last_data_persist = OS::time();
}
void Reticulum::clean_caches() {
TRACE("Cleaning resource and packet caches...");
double now = OS::time();
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
/*
// Clean resource caches
for (auto& filename : OS::list_directory(resourcepath) {
try {
if (filename.length() == (Type::Identity::HASHLENGTH//8)*2) {
char filepath[FILEPATH_MAXSIZE];
snprintf(filepath, FILEPATH_MAXSIZE, "%s/%s", _resourcepath, filename.c_str());
//p mtime = os.path.getmtime(filepath)
//p age = now - mtime
//p if (age > Types::Reticulum.RESOURCE_CACHE) {
//p OS::remove_file(filepath);
//p }
}
}
catch (std::exception& e) {
ERROR("Error while cleaning resources cache, the contained exception was: %s", e.what());
}
}
// Clean packet caches
for (auto& filename : OS::list_directory(_cachepath.c_str())) {
try {
if (filename.length() == (Type::Identity::HASHLENGTH/8)*2) {
char filepath[FILEPATH_MAXSIZE];
snprintf(filepath, FILEPATH_MAXSIZE, "%s/%s", _cachepath, filename.c_str());
//p mtime = os.path.getmtime(filepath)
//p age = now - mtime
//p if (age > Types::Transport::DESTINATION_TIMEOUT) {
//p OS::remove_file(filepath);
//p }
}
}
catch (std::exception& e) {
ERROR("Error while cleaning packet cache, the contained exception was: %s", e.what());
}
}
*/
Transport::clean_caches();
// CBA
Identity::cull_known_destinations();
#endif
}
void Reticulum::clear_caches() {
TRACE("Clearing resource and packet caches...");
try {
char destination_table_path[FILEPATH_MAXSIZE];
snprintf(destination_table_path, FILEPATH_MAXSIZE, "%s/destination_table", _storagepath);
OS::remove_file(destination_table_path);
OS::remove_directory(_cachepath);
#ifdef ARDUINO
char time_offset_path[FILEPATH_MAXSIZE];
snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
OS::remove_file(time_offset_path);
#endif
}
catch (std::exception& e) {
ERRORF("Failed to clear cache file(s), the contained exception was: %s", e.what());
}
}
/*p TODO
void Reticulum::__create_default_config() {
self.config = ConfigObj(__default_rns_config__)
self.config.filename = Reticulum.configpath
if not os.path.isdir(Reticulum.configdir):
os.makedirs(Reticulum.configdir)
self.config.write()
}
void Reticulum::rpc_loop() {
}
void Reticulum::get_interface_stats() const {
}
*/
const std::map<Bytes, Transport::DestinationEntry>& Reticulum::get_path_table() const {
/*
path_table = []
for dst_hash in Transport::destination_table:
entry = {
"hash": dst_hash,
"timestamp": Transport::destination_table[dst_hash][0],
"via": Transport::destination_table[dst_hash][1],
"hops": Transport::destination_table[dst_hash][2],
"expires": Transport::destination_table[dst_hash][3],
"interface": str(Transport::destination_table[dst_hash][5]),
}
path_table.append(entry)
return path_table
*/
return Transport::get_destination_table();
}
const std::map<Bytes, Transport::RateEntry>& Reticulum::get_rate_table() const {
/*
rate_table = []
for dst_hash in Transport::announce_rate_table:
entry = {
"hash": dst_hash,
"last": Transport::announce_rate_table[dst_hash]["last"],
"rate_violations": Transport::announce_rate_table[dst_hash]["rate_violations"],
"blocked_until": Transport::announce_rate_table[dst_hash]["blocked_until"],
"timestamps": Transport::announce_rate_table[dst_hash]["timestamps"],
}
rate_table.append(entry)
return rate_table
*/
return Transport::get_announce_rate_table();
}
bool Reticulum::drop_path(const Bytes& destination) {
return Transport::expire_path(destination);
}
uint16_t Reticulum::drop_all_via(const Bytes& transport_hash) {
uint16_t dropped_count = 0;
//for (auto& destination_hash : Transport::get_destination_table()) {
for (const auto& [destination_hash, destination_entry] : Transport::get_destination_table()) {
if (destination_entry._received_from == transport_hash) {
Transport::expire_path(destination_hash);
++dropped_count;
}
}
return dropped_count;
}
void Reticulum::drop_announce_queues() {
Transport::drop_announce_queues();
}
const std::string Reticulum::get_next_hop_if_name(const Bytes& destination) const {
return Transport::next_hop_interface(destination).name();
}
double Reticulum::get_first_hop_timeout(const Bytes& destination) const {
return Transport::first_hop_timeout(destination);
}
const Bytes Reticulum::get_next_hop(const Bytes& destination) const {
return Transport::next_hop(destination);
}
size_t Reticulum::get_link_count() const {
return Transport::get_link_table().size();
}
/*p
void Reticulum::get_packet_rssi(const Bytes& packet_hash) const {
for entry in Transport::local_client_rssi_cache:
if entry[0] == packet_hash:
return entry[1]
return None
void Reticulum::get_packet_snr(const Bytes& packet_hash) const {
for entry in Transport::local_client_snr_cache:
if entry[0] == packet_hash:
return entry[1]
return None
void Reticulum::get_packet_q(const Bytes& packet_hash) const {
for entry in Transport::local_client_q_cache:
if entry[0] == packet_hash:
return entry[1]
return None
*/

View File

@@ -0,0 +1,182 @@
#pragma once
#include "Transport.h"
#include "Log.h"
#include "Type.h"
#include "Utilities/OS.h"
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <cassert>
#include <stdint.h>
namespace RNS {
class Reticulum {
public:
//z router = None
//z config = None
// The default configuration path will be expanded to a directory
// named ".reticulum" inside the current users home directory
//z userdir = os.path.expanduser("~")
//z configdir = None
//z configpath = ""
//p storagepath = ""
//static std::string _storagepath;
static char _storagepath[Type::Reticulum::FILEPATH_MAXSIZE];
//p cachepath = ""
//static std::string _cachepath;
static char _cachepath[Type::Reticulum::FILEPATH_MAXSIZE];
static const Reticulum& _instance;
static bool __transport_enabled;
static bool __link_mtu_discovery;
static bool __remote_management_enabled;
static bool __use_implicit_proof;
static bool __allow_probes;
static bool panic_on_interface_error;
public:
// Return the currently running Reticulum instance
inline static const Reticulum& get_instance() { return _instance; }
public:
Reticulum();
Reticulum(Type::NoneConstructor none) {
MEM("Reticulum empty object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
Reticulum(const Reticulum& reticulum) : _object(reticulum._object) {
MEM("Reticulum object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
virtual ~Reticulum() {
MEM("Reticulum object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
}
inline Reticulum& operator = (const Reticulum& reticulum) {
_object = reticulum._object;
MEM("Reticulum object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
return *this;
}
inline operator bool() const {
return _object.get() != nullptr;
}
inline bool operator < (const Reticulum& reticulum) const {
return _object.get() < reticulum._object.get();
}
public:
void start();
void loop();
void jobs();
void should_persist_data();
void persist_data();
void clean_caches();
void clear_caches();
//void __create_default_config();
//void rpc_loop();
//void get_interface_stats() const;
const std::map<Bytes, Transport::DestinationEntry>& get_path_table() const;
const std::map<Bytes, Transport::RateEntry>& get_rate_table() const;
bool drop_path(const Bytes& destination);
uint16_t drop_all_via(const Bytes& transport_hash);
void drop_announce_queues();
const std::string get_next_hop_if_name(const Bytes& destination) const;
double get_first_hop_timeout(const Bytes& destination) const;
const Bytes get_next_hop(const Bytes& destination) const;
size_t get_link_count() const;
//void get_packet_rssi(const Bytes& packet_hash) const;
//void get_packet_snr(const Bytes& packet_hash) const;
//void get_packet_q(const Bytes& packet_hash) const;
/*
Returns whether proofs sent are explicit or implicit.
:returns: True if the current running configuration specifies to use implicit proofs. False if not.
*/
inline static bool should_use_implicit_proof() { return __use_implicit_proof; }
/*
Returns whether Transport is enabled for the running
instance.
When Transport is enabled, Reticulum will
route traffic for other peers, respond to path requests
and pass announces over the network.
:returns: True if Transport is enabled, False if not.
*/
inline static bool transport_enabled() { return __transport_enabled; }
inline static void transport_enabled(bool transport_enabled) { __transport_enabled = transport_enabled; }
/*
Returns whether link MTU discovery is enabled for the running
instance.
When link MTU discovery is enabled, Reticulum will
automatically upgrade link MTUs to the highest supported
value, increasing transfer speed and efficiency.
:returns: True if link MTU discovery is enabled, False if not.
*/
inline static bool link_mtu_discovery() { return __link_mtu_discovery; }
inline static void link_mtu_discovery(bool link_mtu_discovery) { __link_mtu_discovery = link_mtu_discovery; }
/*
Returns whether remote management is enabled for the
running instance.
When remote management is enabled, authenticated peers
can remotely query and manage this instance.
:returns: True if remote management is enabled, False if not.
*/
inline static bool remote_management_enabled() { return __remote_management_enabled; }
inline static void remote_management_enabled(bool remote_management_enabled) { __remote_management_enabled = remote_management_enabled; }
inline static bool probe_destination_enabled() { return __allow_probes; }
inline static void probe_destination_enabled(bool allow_probes) { __allow_probes = allow_probes; }
// getters/setters
inline bool is_connected_to_shared_instance() const { assert(_object); return _object->_is_connected_to_shared_instance; }
private:
class Object {
public:
Object() {
MEM("Reticulum data object created, this: " + std::to_string((uintptr_t)this));
}
virtual ~Object() {
MEM("Reticulum data object destroyed, this: " + std::to_string((uintptr_t)this));
}
private:
uint16_t _local_interface_port = 37428;
uint16_t _local_control_port = 37429;
bool _share_instance = true;
//p _rpc_listener = None
//p _ifac_salt = Reticulum.IFAC_SALT
bool _is_shared_instance = false;
bool _is_connected_to_shared_instance = false;
bool _is_standalone_instance = false;
//p _jobs_thread = None
double _last_data_persist = Utilities::OS::time();
double _last_cache_clean = 0.0;
// CBA
double _jobs_last_run = Utilities::OS::time();
friend class Reticulum;
};
std::shared_ptr<Object> _object;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,504 @@
#pragma once
#include "Packet.h"
#include "Bytes.h"
#include "Type.h"
#include <map>
#include <vector>
#include <list>
#include <set>
#include <array>
#include <memory>
#include <functional>
#include <stdint.h>
//#define INTERFACES_SET
//#define INTERFACES_LIST
#define INTERFACES_MAP
//#define DESTINATIONS_SET
#define DESTINATIONS_MAP
namespace RNS {
class Reticulum;
class Identity;
class Destination;
class Interface;
class Link;
class Packet;
class PacketReceipt;
class AnnounceHandler {
public:
// The initialisation method takes the optional
// aspect_filter argument. If aspect_filter is set to
// None, all announces will be passed to the instance.
// If only some announces are wanted, it can be set to
// an aspect string.
AnnounceHandler(const char* aspect_filter = nullptr) { if (aspect_filter != nullptr) _aspect_filter = aspect_filter; }
// This method will be called by Reticulums Transport
// system when an announce arrives that matches the
// configured aspect filter. Filters must be specific,
// and cannot use wildcards.
virtual void received_announce(const Bytes& destination_hash, const Identity& announced_identity, const Bytes& app_data) = 0;
std::string& aspect_filter() { return _aspect_filter; }
private:
std::string _aspect_filter;
};
using HAnnounceHandler = std::shared_ptr<AnnounceHandler>;
/*
Through static methods of this class you can interact with the
Transport system of Reticulum.
*/
class Transport {
public:
class Callbacks {
public:
using receive_packet = void(*)(const Bytes& raw, const Interface& interface);
using transmit_packet = void(*)(const Bytes& raw, const Interface& interface);
using filter_packet = bool(*)(const Packet& packet);
public:
receive_packet _receive_packet = nullptr;
transmit_packet _transmit_packet = nullptr;
filter_packet _filter_packet = nullptr;
friend class Transport;
};
public:
class PacketEntry {
public:
PacketEntry() {}
PacketEntry(const Bytes& raw, double sent_at, const Bytes& destination_hash) :
_raw(raw),
_sent_at(sent_at),
_destination_hash(destination_hash)
{
}
PacketEntry(const Packet& packet) :
_raw(packet.raw()),
_sent_at(packet.sent_at()),
_destination_hash(packet.destination_hash())
{
}
public:
Bytes _raw;
double _sent_at = 0;
Bytes _destination_hash;
bool _cached = false;
#ifndef NDEBUG
inline std::string debugString() const {
std::string dump;
dump = "PacketEntry: destination_hash=" + _destination_hash.toHex() +
" sent_at=" + std::to_string(_sent_at);
return dump;
}
#endif
};
// CBA TODO Analyze safety of using Inrerface references here
// CBA TODO Analyze safety of using Packet references here
class DestinationEntry {
public:
DestinationEntry() {}
DestinationEntry(double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, const Bytes& receiving_interface, const Bytes& packet) :
_timestamp(timestamp),
_received_from(received_from),
_hops(announce_hops),
_expires(expires),
_random_blobs(random_blobs),
_receiving_interface(receiving_interface),
_announce_packet(packet)
{
}
public:
inline Interface receiving_interface() const { return find_interface_from_hash(_receiving_interface); }
inline Packet announce_packet() const { return get_cached_packet(_announce_packet); }
public:
double _timestamp = 0;
Bytes _received_from;
uint8_t _hops = 0;
double _expires = 0;
std::set<Bytes> _random_blobs;
//Interface _receiving_interface = {Type::NONE};
Bytes _receiving_interface;
//const Packet& _announce_packet;
//Packet _announce_packet = {Type::NONE};
Bytes _announce_packet;
inline bool operator < (const DestinationEntry& entry) const {
// sort by ascending timestamp (oldest entries at the top)
return _timestamp < entry._timestamp;
}
#ifndef NDEBUG
inline std::string debugString() const {
std::string dump;
dump = "DestinationEntry: timestamp=" + std::to_string(_timestamp) +
" received_from=" + _received_from.toHex() +
" hops=" + std::to_string(_hops) +
" expires=" + std::to_string(_expires) +
//" random_blobs=" + _random_blobs +
" receiving_interface=" + _receiving_interface.toHex() +
" announce_packet=" + _announce_packet.toHex();
dump += " random_blobs=(";
for (auto& blob : _random_blobs) {
dump += blob.toHex() + ",";
}
dump += ")";
return dump;
}
#endif
};
// CBA TODO Analyze safety of using Inrerface references here
// CBA TODO Analyze safety of using Packet references here
class AnnounceEntry {
public:
AnnounceEntry(double timestamp, double retransmit_timeout, uint8_t retries, const Bytes& received_from, uint8_t hops, const Packet& packet, uint8_t local_rebroadcasts, bool block_rebroadcasts, const Interface& attached_interface) :
_timestamp(timestamp),
_retransmit_timeout(retransmit_timeout),
_retries(retries),
_received_from(received_from),
_hops(hops),
_packet(packet),
_local_rebroadcasts(local_rebroadcasts),
_block_rebroadcasts(block_rebroadcasts),
_attached_interface(attached_interface)
{
}
public:
double _timestamp = 0;
double _retransmit_timeout = 0;
uint8_t _retries = 0;
const Bytes _received_from;
uint8_t _hops = 0;
// CBA Storing packet reference causes memory issues, presumably because orignal packet is being destroyed
// MUST use instance instad of reference!!!
//const Packet& _packet;
const Packet _packet = {Type::NONE};
uint8_t _local_rebroadcasts = 0;
bool _block_rebroadcasts = false;
const Interface _attached_interface = {Type::NONE};
};
// CBA TODO Analyze safety of using Inrerface references here
class LinkEntry {
public:
LinkEntry(double timestamp, const Bytes& next_hop, const Interface& outbound_interface, uint8_t remaining_hops, const Interface& receiving_interface, uint8_t hops, const Bytes& destination_hash, bool validated, double proof_timeout) :
_timestamp(timestamp),
_next_hop(next_hop),
_outbound_interface(outbound_interface),
_remaining_hops(remaining_hops),
_receiving_interface(receiving_interface),
_hops(hops),
_destination_hash(destination_hash),
_validated(validated),
_proof_timeout(proof_timeout)
{
}
public:
double _timestamp = 0;
const Bytes _next_hop;
const Interface _outbound_interface = {Type::NONE};
uint8_t _remaining_hops = 0;
Interface _receiving_interface = {Type::NONE};
uint8_t _hops = 0;
const Bytes _destination_hash;
bool _validated = false;
double _proof_timeout = 0;
};
// CBA TODO Analyze safety of using Inrerface references here
class ReverseEntry {
public:
ReverseEntry(const Interface& receiving_interface, const Interface& outbound_interface, double timestamp) :
_receiving_interface(receiving_interface),
_outbound_interface(outbound_interface),
_timestamp(timestamp)
{
}
public:
Interface _receiving_interface = {Type::NONE};
const Interface _outbound_interface = {Type::NONE};
double _timestamp = 0;
};
// CBA TODO Analyze safety of using Inrerface references here
class PathRequestEntry {
public:
PathRequestEntry(const Bytes& destination_hash, double timeout, const Interface& requesting_interface) :
_destination_hash(destination_hash),
_timeout(timeout),
_requesting_interface(requesting_interface)
{
}
public:
const Bytes _destination_hash;
double _timeout = 0;
const Interface _requesting_interface = {Type::NONE};
};
/*
// CBA TODO Analyze safety of using Inrerface references here
class SerialisedEntry {
public:
SerialisedEntry(const Bytes& destination_hash, double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, Interface& receiving_interface, const Packet& packet) :
_destination_hash(destination_hash),
_timestamp(timestamp),
_hops(announce_hops),
_expires(expires),
_random_blobs(random_blobs),
_receiving_interface(receiving_interface),
_announce_packet(packet)
{
}
public:
const Bytes _destination_hash;
double _timestamp = 0;
const Bytes _received_from;
uint8_t _hops = 0;
double _expires = 0;
std::set<Bytes> _random_blobs;
Interface _receiving_interface = {Type::NONE};
Packet _announce_packet = {Type::NONE};
};
*/
// CBA TODO Analyze safety of using Inrerface references here
class TunnelEntry {
public:
TunnelEntry(const Bytes& tunnel_id, const Bytes& interface_hash, double expires) :
_tunnel_id(tunnel_id),
_interface_hash(interface_hash),
_expires(expires)
{
}
public:
const Bytes _tunnel_id;
const Bytes _interface_hash;
std::map<Bytes, DestinationEntry> _serialised_paths;
double _expires = 0;
};
class RateEntry {
public:
RateEntry(double now) :
_last(now)
{
_timestamps.push_back(now);
}
public:
double _last = 0.0;
double _rate_violations = 0.0;
double _blocked_until = 0.0;
std::vector<double> _timestamps;
};
public:
static void start(const Reticulum& reticulum_instance);
static void loop();
static void jobs();
static void transmit(Interface& interface, const Bytes& raw);
static bool outbound(Packet& packet);
static bool packet_filter(const Packet& packet);
//static void inbound(const Bytes& raw, const Interface& interface = {Type::NONE});
static void inbound(const Bytes& raw, const Interface& interface);
static void inbound(const Bytes& raw);
static void synthesize_tunnel(const Interface& interface);
static void tunnel_synthesize_handler(const Bytes& data, const Packet& packet);
static void handle_tunnel(const Bytes& tunnel_id, const Interface& interface);
static void register_interface(Interface& interface);
static void deregister_interface(const Interface& interface);
inline static const std::map<Bytes, Interface&> get_interfaces() { return _interfaces; }
static void register_destination(Destination& destination);
static void deregister_destination(const Destination& destination);
static void register_link(Link& link);
static void activate_link(Link& link);
static void register_announce_handler(HAnnounceHandler handler);
static void deregister_announce_handler(HAnnounceHandler handler);
static Interface find_interface_from_hash(const Bytes& interface_hash);
static bool should_cache_packet(const Packet& packet);
static bool cache_packet(const Packet& packet, bool force_cache = false);
static Packet get_cached_packet(const Bytes& packet_hash);
static bool clear_cached_packet(const Bytes& packet_hash);
static bool cache_request_packet(const Packet& packet);
static void cache_request(const Bytes& packet_hash, const Destination& destination);
static bool remove_path(const Bytes& destination_hash);
static bool has_path(const Bytes& destination_hash);
static uint8_t hops_to(const Bytes& destination_hash);
static Bytes next_hop(const Bytes& destination_hash);
static Interface next_hop_interface(const Bytes& destination_hash);
static uint32_t next_hop_interface_bitrate(const Bytes& destination_hash);
static uint16_t next_hop_interface_hw_mtu(const Bytes& destination_hash);
static double next_hop_per_bit_latency(const Bytes& destination_hash);
static double next_hop_per_byte_latency(const Bytes& destination_hash);
static double first_hop_timeout(const Bytes& destination_hash);
static double extra_link_proof_timeout(const Interface& interface);
static bool expire_path(const Bytes& destination_hash);
//static void request_path(const Bytes& destination_hash, const Interface& on_interface = {Type::NONE}, const Bytes& tag = {}, bool recursive = false);
static void request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag = {}, bool recursive = false);
static void request_path(const Bytes& destination_hash);
static void path_request_handler(const Bytes& data, const Packet& packet);
static void path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id = {}, const Bytes& tag = {});
static bool from_local_client(const Packet& packet);
static bool is_local_client_interface(const Interface& interface);
static bool interface_to_shared_instance(const Interface& interface);
static void detach_interfaces();
static void shared_connection_disappeared();
static void shared_connection_reappeared();
static void drop_announce_queues();
static uint64_t announce_emitted(const Packet& packet);
static void write_packet_hashlist();
static bool read_path_table();
static bool write_path_table();
static void read_tunnel_table();
static void write_tunnel_table();
static void persist_data();
static void clean_caches();
static void dump_stats();
static void exit_handler();
static uint16_t remove_reverse_entries(const std::vector<Bytes>& hashes);
static uint16_t remove_links(const std::vector<Bytes>& hashes);
static uint16_t remove_paths(const std::vector<Bytes>& hashes);
static uint16_t remove_discovery_path_requests(const std::vector<Bytes>& hashes);
static uint16_t remove_tunnels(const std::vector<Bytes>& hashes);
static Destination find_destination_from_hash(const Bytes& destination_hash);
// CBA
static void cull_path_table();
// getters/setters
static inline void set_receive_packet_callback(Callbacks::receive_packet callback) { _callbacks._receive_packet = callback; }
static inline void set_transmit_packet_callback(Callbacks::transmit_packet callback) { _callbacks._transmit_packet = callback; }
static inline void set_filter_packet_callback(Callbacks::filter_packet callback) { _callbacks._filter_packet = callback; }
static inline const Reticulum& reticulum() { return _owner; }
static inline const Identity& identity() { return _identity; }
inline static uint16_t path_table_maxsize() { return _path_table_maxsize; }
inline static void path_table_maxsize(uint16_t path_table_maxsize) { _path_table_maxsize = path_table_maxsize; }
inline static uint16_t probe_destination_enabled() { return _path_table_maxpersist; }
inline static void path_table_maxpersist(uint16_t path_table_maxpersist) { _path_table_maxpersist = path_table_maxpersist; }
// CBA TEST
static inline void identity(Identity& identity) { _identity = identity; }
inline static const std::map<Bytes, DestinationEntry>& get_destination_table() { return _destination_table; }
inline static const std::map<Bytes, RateEntry>& get_announce_rate_table() { return _announce_rate_table; }
inline static const std::map<Bytes, LinkEntry>& get_link_table() { return _link_table; }
private:
// CBA MUST use references to interfaces here in order for virtul overrides for send/receive to work
#if defined(INTERFACES_SET)
// set sorted, can use find
//static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _interfaces; // All active interfaces
static std::set<std::reference_wrapper<Interface>, std::less<Interface>> _interfaces; // All active interfaces
#elif defined(INTERFACES_LIST)
// list is unsorted, can't use find
static std::list<std::reference_wrapper<Interface>> _interfaces; // All active interfaces
#elif defined(INTERFACES_MAP)
// map is sorted, can use find
static std::map<Bytes, Interface&> _interfaces; // All active interfaces
#endif
#if defined(DESTINATIONS_SET)
static std::set<Destination> _destinations; // All active destinations
#elif defined(DESTINATIONS_MAP)
static std::map<Bytes, Destination> _destinations; // All active destinations
#endif
// CBA TODO: Reconsider using std::set for enforcing uniqueness. Maybe consider std::map keyed on hash instead
static std::set<Link> _pending_links; // Links that are being established
static std::set<Link> _active_links; // Links that are active
static std::set<Bytes> _packet_hashlist; // A list of packet hashes for duplicate detection
static std::list<PacketReceipt> _receipts; // Receipts of all outgoing packets for proof processing
// TODO: "destination_table" should really be renamed to "path_table"
// Notes on memory usage: 1 megabyte of memory can store approximately
// 55.100 path table entries or approximately 22.300 link table entries.
static std::map<Bytes, AnnounceEntry> _announce_table; // A table for storing announces currently waiting to be retransmitted
static std::map<Bytes, DestinationEntry> _destination_table; // A lookup table containing the next hop to a given destination
static std::map<Bytes, ReverseEntry> _reverse_table; // A lookup table for storing packet hashes used to return proofs and replies
static std::map<Bytes, LinkEntry> _link_table; // A lookup table containing hops for links
static std::map<Bytes, AnnounceEntry> _held_announces; // A table containing temporarily held announce-table entries
static std::set<HAnnounceHandler> _announce_handlers; // A table storing externally registered announce handlers
static std::map<Bytes, TunnelEntry> _tunnels; // A table storing tunnels to other transport instances
static std::map<Bytes, RateEntry> _announce_rate_table; // A table for keeping track of announce rates
static std::map<Bytes, double> _path_requests; // A table for storing path request timestamps
static std::map<Bytes, PathRequestEntry> _discovery_path_requests; // A table for keeping track of path requests on behalf of other nodes
static std::set<Bytes> _discovery_pr_tags; // A table for keeping track of tagged path requests
// Transport control destinations are used
// for control purposes like path requests
static std::set<Destination> _control_destinations;
static std::set<Bytes> _control_hashes;
// Interfaces for communicating with
// local clients connected to a shared
// Reticulum instance
//static std::set<Interface> _local_client_interfaces;
static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _local_client_interfaces;
static std::map<Bytes, const Interface&> _pending_local_path_requests;
// CBA
static std::map<Bytes, PacketEntry> _packet_table; // A lookup table containing announce packets for known paths
//z _local_client_rssi_cache = []
//z _local_client_snr_cache = []
static uint16_t _LOCAL_CLIENT_CACHE_MAXSIZE;
static double _start_time;
static bool _jobs_locked;
static bool _jobs_running;
static float _job_interval;
static double _jobs_last_run;
static double _links_last_checked;
static float _links_check_interval;
static double _receipts_last_checked;
static float _receipts_check_interval;
static double _announces_last_checked;
static float _announces_check_interval;
static double _tables_last_culled;
static float _tables_cull_interval;
static bool _saving_path_table;
static uint16_t _hashlist_maxsize;
static uint16_t _max_pr_tags;
// CBA
static uint16_t _path_table_maxsize;
static uint16_t _path_table_maxpersist;
static double _last_saved;
static float _save_interval;
static uint32_t _destination_table_crc;
static Reticulum _owner;
static Identity _identity;
// CBA
static Callbacks _callbacks;
// CBA Stats
static uint32_t _packets_sent;
static uint32_t _packets_received;
static uint32_t _destinations_added;
static size_t _last_memory;
static size_t _last_flash;
};
template <typename M, typename S>
void MapToValues(const M& m, S& s) {
for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
s.insert(it->second);
}
}
template <typename M, typename S>
void MapToPairs(const M& m, S& s) {
for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
s.push_back(*it);
}
}
}

549
lib/microReticulum/src/Type.h Executable file
View File

@@ -0,0 +1,549 @@
#pragma once
#include "Log.h"
#include <stdint.h>
#include <math.h>
namespace RNS { namespace Type {
// generic empty object constructor type
enum NoneConstructor {
NONE
};
namespace Persistence {
//static const uint16_t DOCUMENT_MAXSIZE = 1024;
static const uint16_t DOCUMENT_MAXSIZE = 8192;
//static const uint16_t DOCUMENT_MAXSIZE = 16384;
static const uint16_t BUFFER_MAXSIZE = Persistence::DOCUMENT_MAXSIZE * 1.5; // Json write buffer of 1.5 times document seems to be sufficient
}
namespace Cryptography {
namespace Fernet {
static const uint8_t FERNET_OVERHEAD = 48; // Bytes
}
namespace Token {
static const uint8_t TOKEN_OVERHEAD = 48; // Bytes
enum token_mode {
MODE_AES = 0x01,
MODE_AES_128_CBC = 0x02,
MODE_AES_256_CBC = 0x03,
};
}
}
namespace Reticulum {
/*
The MTU that Reticulum adheres to, and will expect other peers to
adhere to. By default, the MTU is 507 bytes. In custom RNS network
implementations, it is possible to change this value, but doing so will
completely break compatibility with all other RNS networks. An identical
MTU is a prerequisite for peers to communicate in the same network.
Unless you really know what you are doing, the MTU should be left at
the default value.
Future minimum will probably be locked in at 251 bytes to support
networks with segments of different MTUs. Absolute minimum is 219.
*/
static const uint16_t MTU = 500;
/*
Whether automatic link MTU discovery is enabled by default in this
release. Link MTU discovery significantly increases throughput over
fast links, but requires all intermediary hops to also support it.
Support for this feature was added in RNS version 0.9.0. This option
will become enabled by default in the near future. Please update your
RNS instances.
*/
static const bool LINK_MTU_DISCOVERY = true;
static const uint16_t MAX_QUEUED_ANNOUNCES = 16384;
static const uint32_t QUEUED_ANNOUNCE_LIFE = 60*60*24;
static const uint8_t ANNOUNCE_CAP = 2;
/*
The maximum percentage of interface bandwidth that, at any given time,
may be used to propagate announces. If an announce was scheduled for
broadcasting on an interface, but doing so would exceed the allowed
bandwidth allocation, the announce will be queued for transmission
when there is bandwidth available.
Reticulum will always prioritise propagating announces with fewer
hops, ensuring that distant, large networks with many peers on fast
links don't overwhelm the capacity of smaller networks on slower
mediums. If an announce remains queued for an extended amount of time,
it will eventually be dropped.
This value will be applied by default to all created interfaces,
but it can be configured individually on a per-interface basis.
*/
static const uint16_t MINIMUM_BITRATE = 500;
// TODO: To reach the 300bps level without unreasonably impacting
// performance on faster links, we need a mechanism for setting
// this value more intelligently. One option could be inferring it
// from interface speed, but a better general approach would most
// probably be to let Reticulum somehow continously build a map of
// per-hop latencies and use this map for the timeout calculation.
static const uint8_t DEFAULT_PER_HOP_TIMEOUT = 6;
static const uint16_t HASHLENGTH = 256; // In bits
// Length of truncated hashes in bits.
static const uint16_t TRUNCATED_HASHLENGTH = 128; // In bits
static const uint16_t HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH/8)*1; // In bytes
static const uint16_t HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH/8)*2; // In bytes
static const uint16_t IFAC_MIN_SIZE = 1;
//z IFAC_SALT = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8")
static const uint16_t MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE;
static const uint32_t RESOURCE_CACHE = 60*60*24;
// CBA TEST
//static const uint16_t JOB_INTERVAL = 60*5;
static const uint16_t JOB_INTERVAL = 60;
// CBA TEST
//static const uint16_t CLEAN_INTERVAL = 60*15;
static const uint16_t CLEAN_INTERVAL = 60;
// CBA MCU
//static const uint16_t PERSIST_INTERVAL = 60*60*12;
// CBA TEST
//static const uint16_t PERSIST_INTERVAL = 60*60;
static const uint16_t PERSIST_INTERVAL = 60;
static const uint16_t GRACIOUS_PERSIST_INTERVAL = 60*5;
static const uint8_t DESTINATION_LENGTH = TRUNCATED_HASHLENGTH/8; // In bytes
// CBA MCU
//static const uint8_t FILEPATH_MAXSIZE = 64;
static const uint8_t FILEPATH_MAXSIZE = 96;
}
namespace Identity {
// The curve used for Elliptic Curve DH key exchanges
//static const char CURVE[] = "Curve25519";
static constexpr const char* CURVE = "Curve25519";
// X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
static const uint16_t KEYSIZE = 256*2;
// X.25519 ratchet key size in bits.
static const uint16_t RATCHETSIZE = 256;
/*
The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
announced ratchet, and remember it for up to ``RATCHET_EXPIRY`` since receiving it, after which it will be discarded.
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.
*/
static const uint32_t RATCHET_EXPIRY = 60*60*24*30;
// Non-configurable constants
static const uint8_t FERNET_OVERHEAD = Cryptography::Fernet::FERNET_OVERHEAD;
static const uint8_t TOKEN_OVERHEAD = Cryptography::Token::TOKEN_OVERHEAD;
static const uint8_t AES128_BLOCKSIZE = 16; // In bytes
static const uint16_t HASHLENGTH = Reticulum::HASHLENGTH; // In bits
static const uint16_t SIGLENGTH = KEYSIZE; // In bits
static const uint8_t NAME_HASH_LENGTH = 80;
static const uint8_t RANDOM_HASH_LENGTH = 80;
// Constant specifying the truncated hash length (in bits) used by Reticulum
// for addressable hashes and other purposes. Non-configurable.
static const uint16_t TRUNCATED_HASHLENGTH = Reticulum::TRUNCATED_HASHLENGTH; // In bits
static const uint16_t DERIVED_KEY_LENGTH = 512/8;
static const uint16_t DERIVED_KEY_LENGTH_LEGACY = 256/8;
}
namespace Destination {
enum types {
SINGLE = 0x00,
GROUP = 0x01,
PLAIN = 0x02,
LINK = 0x03,
};
enum proof_strategies {
PROVE_NONE = 0x21,
PROVE_APP = 0x22,
PROVE_ALL = 0x23,
};
enum request_policies {
ALLOW_NONE = 0x00,
ALLOW_ALL = 0x01,
ALLOW_LIST = 0x02,
};
enum directions {
IN = 0x11,
OUT = 0x12,
};
const uint8_t PR_TAG_WINDOW = 30;
}
namespace Link {
// The curve used for Elliptic Curve DH key exchanges
static constexpr const char* CURVE = Identity::CURVE;
static const uint16_t ECPUBSIZE = 32+32;
static const uint8_t KEYSIZE = 32;
//static const uint16_t MDU = floor((Reticulum::MTU-Reticulum::IFAC_MIN_SIZE-Reticulum::HEADER_MINSIZE-Identity::FERNET_OVERHEAD)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
static const uint16_t MDU = ((Reticulum::MTU-Reticulum::IFAC_MIN_SIZE-Reticulum::HEADER_MINSIZE-Identity::FERNET_OVERHEAD)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
// Timeout for link establishment in seconds per hop to destination.
static const uint8_t ESTABLISHMENT_TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT;
static const uint8_t LINK_MTU_SIZE = 3;
static const uint8_t TRAFFIC_TIMEOUT_MIN_MS = 5;
// RTT timeout factor used in link timeout calculation.
static const uint8_t TRAFFIC_TIMEOUT_FACTOR = 6;
static const float KEEPALIVE_MAX_RTT = 1.75;
static const uint8_t KEEPALIVE_TIMEOUT_FACTOR = 4;
// Grace period in seconds used in link timeout calculation.
static const uint8_t STALE_GRACE = 2;
// Interval for sending keep-alive packets on established links in seconds.
static const uint16_t KEEPALIVE = 360;
/*
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.
If after this no traffic or keep-alive packets are received within ``RTT`` *
``KEEPALIVE_TIMEOUT_FACTOR`` + ``STALE_GRACE``, the link is considered timed out,
and will be torn down.
*/
static const uint8_t STALE_FACTOR = 2;
static const uint16_t STALE_TIME = 2*KEEPALIVE;
static const uint8_t WATCHDOG_MAX_SLEEP = 5;
static const uint64_t MTU_BYTEMASK = 0x1FFFFF;
static const uint8_t MODE_BYTEMASK = 0xE0;
enum status {
PENDING = 0x00,
HANDSHAKE = 0x01,
ACTIVE = 0x02,
STALE = 0x03,
CLOSED = 0x04
};
enum teardown_reason {
TEARDOWN_NONE = 0x00,
TIMEOUT = 0x01,
INITIATOR_CLOSED = 0x02,
DESTINATION_CLOSED = 0x03,
};
enum resource_strategy {
ACCEPT_NONE = 0x00,
ACCEPT_APP = 0x01,
ACCEPT_ALL = 0x02,
};
enum link_mode {
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,
};
/*
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"}
*/
}
namespace RequestReceipt {
enum status {
FAILED = 0x00,
SENT = 0x01,
DELIVERED = 0x02,
RECEIVING = 0x03,
READY = 0x04,
};
}
namespace Interface {
// Interface mode definitions
enum modes {
MODE_NONE = 0x00,
MODE_FULL = 0x01,
MODE_POINT_TO_POINT = 0x04,
MODE_ACCESS_POINT = 0x08,
MODE_ROAMING = 0x10,
MODE_BOUNDARY = 0x20,
MODE_GATEWAY = 0x40,
};
}
namespace Packet {
// Packet types
enum types {
DATA = 0x00, // Data packets
ANNOUNCE = 0x01, // Announces
LINKREQUEST = 0x02, // Link requests
PROOF = 0x03, // Proofs
};
// Header types
enum header_types {
HEADER_1 = 0x00, // Normal header format
HEADER_2 = 0x01, // Header format used for packets in transport
};
// Packet context types
enum context_types {
CONTEXT_NONE = 0x00, // Generic data packet
RESOURCE = 0x01, // Packet is part of a resource
RESOURCE_ADV = 0x02, // Packet is a resource advertisement
RESOURCE_REQ = 0x03, // Packet is a resource part request
RESOURCE_HMU = 0x04, // Packet is a resource hashmap update
RESOURCE_PRF = 0x05, // Packet is a resource proof
RESOURCE_ICL = 0x06, // Packet is a resource initiator cancel message
RESOURCE_RCL = 0x07, // Packet is a resource receiver cancel message
CACHE_REQUEST = 0x08, // Packet is a cache request
REQUEST = 0x09, // Packet is a request
RESPONSE = 0x0A, // Packet is a response to a request
PATH_RESPONSE = 0x0B, // Packet is a response to a path request
COMMAND = 0x0C, // Packet is a command
COMMAND_STATUS = 0x0D, // Packet is a status of an executed command
CHANNEL = 0x0E, // Packet contains link channel data
KEEPALIVE = 0xFA, // Packet is a keepalive packet
LINKIDENTIFY = 0xFB, // Packet is a link peer identification proof
LINKCLOSE = 0xFC, // Packet is a link close message
LINKPROOF = 0xFD, // Packet is a link packet proof
LRRTT = 0xFE, // Packet is a link request round-trip time measurement
LRPROOF = 0xFF, // Packet is a link request proof
};
// Context flag values
enum context_flags {
FLAG_SET = 0x01,
FLAG_UNSET = 0x00,
};
// This is used to calculate allowable
// payload sizes
static const uint16_t HEADER_MAXSIZE = Reticulum::HEADER_MAXSIZE;
static const uint16_t MDU = Reticulum::MDU;
// With an MTU of 500, the maximum of data we can
// send in a single encrypted packet is given by
// the below calculation; 383 bytes.
//static const uint16_t ENCRYPTED_MDU = floor((Reticulum::MDU-Identity::FERNET_OVERHEAD-Identity::KEYSIZE/16)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
//static const uint16_t ENCRYPTED_MDU;
static const uint16_t ENCRYPTED_MDU = ((Reticulum::MDU-Identity::FERNET_OVERHEAD-Identity::KEYSIZE/16)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
// The maximum size of the payload data in a single encrypted packet
static const uint16_t PLAIN_MDU = MDU;
// The maximum size of the payload data in a single unencrypted packet
static const uint8_t TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT;
}
namespace PacketReceipt {
// Receipt status constants
enum Status {
FAILED = 0x00,
SENT = 0x01,
DELIVERED = 0x02,
CULLED = 0xFF
};
static const uint16_t EXPL_LENGTH = Identity::HASHLENGTH / 8 + Identity::SIGLENGTH / 8;
static const uint16_t IMPL_LENGTH = Identity::SIGLENGTH / 8;
}
namespace Transport {
enum types {
BROADCAST = 0x00,
TRANSPORT = 0x01,
RELAY = 0x02,
TUNNEL = 0x03,
NONE = 0xFF,
};
enum reachabilities {
REACHABILITY_UNREACHABLE = 0x00,
REACHABILITY_DIRECT = 0x01,
REACHABILITY_TRANSPORT = 0x02,
};
enum state {
STATE_UNKNOWN = 0x00,
STATE_UNRESPONSIVE = 0x01,
STATE_RESPONSIVE = 0x02,
};
static constexpr const char* APP_NAME = "rnstransport";
// Maximum amount of hops that Reticulum will transport a packet.
static const uint8_t PATHFINDER_M = 128; // Max hops
static const uint8_t PATHFINDER_R = 1; // Retransmit retries
static const uint8_t PATHFINDER_G = 5; // Retry grace period
static constexpr const float PATHFINDER_RW = 0.5; // Random window for announce rebroadcast
// TODO: Calculate an optimal number for this in
// various situations
static const uint8_t LOCAL_REBROADCASTS_MAX = 2; // How many local rebroadcasts of an announce is allowed
static const uint8_t PATH_REQUEST_TIMEOUT = 15; // Default timuout for client path requests in seconds
static constexpr const float PATH_REQUEST_GRACE = 0.35; // Grace time before a path announcement is made, allows directly reachable peers to respond first
static const uint8_t PATH_REQUEST_RW = 2; // Path request random window
static const uint8_t PATH_REQUEST_MI = 5; // Minimum interval in seconds for automated path requests
static constexpr const float LINK_TIMEOUT = Link::STALE_TIME * 1.25;
static const uint16_t REVERSE_TIMEOUT = 30*60; // Reverse table entries are removed after 30 minutes
// CBA MCU
//static const uint16_t MAX_RECEIPTS = 1024; // Maximum number of receipts to keep track of
static const uint16_t MAX_RECEIPTS = 20; // Maximum number of receipts to keep track of
static const uint8_t MAX_RATE_TIMESTAMPS = 16; // Maximum number of announce timestamps to keep per destination
static const uint8_t PERSIST_RANDOM_BLOBS = 32; // Maximum number of random blobs per destination to persist to disk
static const uint8_t MAX_RANDOM_BLOBS = 64; // Maximum number of random blobs per destination to keep in memory
// CBA MCU
//static const uint32_t DESTINATION_TIMEOUT = 60*60*24*7; // Destination table entries are removed if unused for one week
//static const uint32_t PATHFINDER_E = 60*60*24*7; // Path expiration of one week
//static const uint32_t AP_PATH_TIME = 60*60*24; // Path expiration of one day for Access Point paths
//static const uint32_t ROAMING_PATH_TIME = 60*60*6; // Path expiration of 6 hours for Roaming paths
static const uint32_t DESTINATION_TIMEOUT = 60*60*24*1; // Destination table entries are removed if unused for one day
static const uint32_t PATHFINDER_E = 60*60*24*1; // Path expiration of one day
static const uint32_t AP_PATH_TIME = 60*60*6; // Path expiration of 6 hours for Access Point paths
static const uint32_t ROAMING_PATH_TIME = 60*60*1; // Path expiration of 1 hour for Roaming paths
static const uint16_t LOCAL_CLIENT_CACHE_MAXSIZE = 512;
}
namespace Resource {
// The initial window size at beginning of transfer
static const uint8_t WINDOW = 4;
// Absolute minimum window size during transfer
static const uint8_t WINDOW_MIN = 1;
// The maximum window size for transfers on slow links
static const uint8_t WINDOW_MAX_SLOW = 10;
// The maximum window size for transfers on fast links
static const uint8_t WINDOW_MAX_FAST = 75;
// For calculating maps and guard segments, this
// must be set to the global maximum window.
static const uint8_t WINDOW_MAX = WINDOW_MAX_FAST;
// If the fast rate is sustained for this many request
// rounds, the fast link window size will be allowed.
static const uint8_t FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2;
// If the RTT rate is higher than this value,
// the max window size for fast links will be used.
// The default is 50 Kbps (the value is stored in
// bytes per second, hence the "/ 8").
static const uint16_t RATE_FAST = (50*1000) / 8;
// The minimum allowed flexibility of the window size.
// The difference between window_max and window_min
// will never be smaller than this value.
static const uint8_t WINDOW_FLEXIBILITY = 4;
// Number of bytes in a map hash
static const uint8_t MAPHASH_LEN = 4;
static const uint16_t SDU = Packet::MDU;
static const uint8_t RANDOM_HASH_SIZE = 4;
// This is an indication of what the
// maximum size a resource should be, if
// 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.
static const uint32_t MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1;
static const uint8_t RESPONSE_MAX_GRACE_TIME = 10;
// The maximum size to auto-compress with
// bz2 before sending.
static const uint32_t AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE;
static const uint8_t PART_TIMEOUT_FACTOR = 4;
static const uint8_t PART_TIMEOUT_FACTOR_AFTER_RTT = 2;
static const uint8_t MAX_RETRIES = 8;
static const uint8_t MAX_ADV_RETRIES = 4;
static const uint8_t SENDER_GRACE_TIME = 10;
static const float RETRY_GRACE_TIME = 0.25;
static const float PER_RETRY_DELAY = 0.5;
static const uint8_t WATCHDOG_MAX_SLEEP = 1;
static const uint8_t HASHMAP_IS_NOT_EXHAUSTED = 0x00;
static const uint8_t HASHMAP_IS_EXHAUSTED = 0xFF;
// Status constants
enum status {
NONE = 0x00,
QUEUED = 0x01,
ADVERTISED = 0x02,
TRANSFERRING = 0x03,
AWAITING_PROOF = 0x04,
ASSEMBLING = 0x05,
COMPLETE = 0x06,
FAILED = 0x07,
CORRUPT = 0x08
};
namespace ResourceAdvertisement {
static const uint8_t OVERHEAD = 134;
static const uint16_t HASHMAP_MAX_LEN = floor((Link::MDU-OVERHEAD)/Resource::MAPHASH_LEN);
//static const uint16_t HASHMAP_MAX_LEN = ((Link::MDU-OVERHEAD)/Resource::MAPHASH_LEN);
static const uint16_t COLLISION_GUARD_SIZE = 2*Resource::WINDOW_MAX+HASHMAP_MAX_LEN;
//assert HASHMAP_MAX_LEN > 0, "The configured MTU is too small to include any map hashes in resource advertisments"
}
}
namespace Channel {
}
} }

View File

@@ -0,0 +1,16 @@
#include "Crc.h"
using namespace RNS::Utilities;
/*static*/ uint32_t Crc::crc32(uint32_t crc, const uint8_t* buf, size_t size) {
const unsigned char *data = (const unsigned char *)buf;
if (data == NULL)
return 0;
crc ^= 0xffffffff;
while (size--) {
crc ^= *data++;
for (unsigned k = 0; k < 8; k++)
crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1;
}
return crc ^ 0xffffffff;
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
#include <string.h>
namespace RNS { namespace Utilities {
class Crc {
public:
static uint32_t crc32(uint32_t crc, const uint8_t* buffer, size_t size);
static inline uint32_t crc32(uint32_t crc, uint8_t byte) { return crc32(crc, &byte, sizeof(byte)); }
static inline uint32_t crc32(uint32_t crc, const char* str) { return crc32(crc, (const uint8_t*)str, strlen(str)); }
};
} }

View File

@@ -0,0 +1,289 @@
#include "OS.h"
#include "../Type.h"
#include "../Log.h"
using namespace RNS;
using namespace RNS::Utilities;
#if defined(RNS_USE_ALLOCATOR)
#if defined(RNS_USE_TLSF)
#if defined(ESP32)
//#define BUFFER_SIZE 1024 * 80
#define BUFFER_SIZE 0
#define BUFFER_FRACTION 0.8
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
//#define BUFFER_SIZE 1024 * 80
#define BUFFER_SIZE 0
#define BUFFER_FRACTION 0.8
#else
#define BUFFER_SIZE 1024 * 1000
#define BUFFER_FRACTION 0
#endif
bool _tlsf_init = false;
//char _tlsf_msg[256] = "";
size_t _buffer_size = BUFFER_SIZE;
size_t _contiguous_size = 0;
/*static*/ //tlsf_t OS::_tlsf = tlsf_create_with_pool(malloc(1024 * 1024), 1024 * 1024);
/*static*/ tlsf_t OS::_tlsf = nullptr;
#endif
uint32_t _new_count = 0;
uint32_t _new_fault = 0;
uint64_t _new_size = 0;
uint32_t _delete_count = 0;
uint32_t _delete_fault = 0;
size_t _min_size = 0;
size_t _max_size = 0;
// CBA Added attribute weak to avoid collision with new override on nrf52
void* operator new(size_t size) {
//__attribute__((weak)) void* operator new(size_t size) {
#if defined(RNS_USE_TLSF)
//if (OS::_tlsf == nullptr) {
if (!_tlsf_init) {
_tlsf_init = true;
#if defined(ESP32)
// CBA Still unknown why the call to tlsf_create_with_pool() is so flaky on ESP32 with calculated buffer size. Reuires more research and unit tests.
_contiguous_size = ESP.getMaxAllocHeap();
TRACEF("contiguous_size: %u", _contiguous_size);
if (_buffer_size == 0) {
// CBA NOTE Using fp mathhere somehow causes tlsf_create_with_pool() to fail.
//_buffer_size = (size_t)(_contiguous_size * BUFFER_FRACTION);
// Compute 80% exactly using integers
_buffer_size = (_contiguous_size * 4) / 5;
}
// Round DOWN to TLSF alignment
size_t align = tlsf_align_size();
_buffer_size &= ~(align - 1);
void* raw_buffer = (void*)aligned_alloc(align, _buffer_size);
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
_contiguous_size = dbgHeapFree();
TRACEF("contiguous_size: %u", _contiguous_size);
if (_buffer_size == 0) {
_buffer_size = (size_t)(_contiguous_size * BUFFER_FRACTION);
}
// For NRF52 round to kB
_buffer_size = (size_t)(_buffer_size / 1024) * 1024;
TRACEF("buffer_size: %u", _buffer_size);
void* raw_buffer = malloc(_buffer_size);
#else
_buffer_size = (size_t)BUFFER_SIZE;
TRACEF("buffer_size: %u", _buffer_size);
void* raw_buffer = malloc(_buffer_size);
#endif
if (raw_buffer == nullptr) {
ERROR("-- allocation for tlsf FAILED");
//strcpy(_tlsf_msg, "-- allocation for tlsf FAILED!!!");
}
else {
#if 1
OS::_tlsf = tlsf_create_with_pool(raw_buffer, _buffer_size);
//if (OS::_tlsf == nullptr) {
// sprintf(_tlsf_msg, "initialization of tlsf with align=%d, contiguous=%d, size=%d FAILED!!!", tlsf_align_size(), _contiguous_size, _buffer_size);
//}
//else {
// sprintf(_tlsf_msg, "initialization of tlsf with align=%d, contiguous=%d, size=%d SUCCESSFUL!!!", tlsf_align_size(), _contiguous_size, _buffer_size);
//}
#else
Serial.print("raw_buffer: ");
Serial.println((long)raw_buffer);
Serial.print("align_size: ");
Serial.println((long)tlsf_align_size());
void* aligned_buffer = (void*)(((size_t)raw_buffer + (tlsf_align_size() - 1)) & ~(tlsf_align_size() - 1));
Serial.print("aligned_buffer: ");
Serial.println((long)aligned_buffer);
OS::_tlsf = tlsf_create_with_pool(aligned_buffer, BUFFER_SIZE-(size_t)((uint32_t)aligned_buffer - (uint32_t)raw_buffer));
//tlfs = tlsf_create_with_pool(aligned_buffer, buffer_size--(size_t)((uint32_t)aligned_buffer - (uint32_t)raw_buffer));
#endif
if (OS::_tlsf == nullptr) {
ERROR("-- initialization of tlsf FAILED");
}
}
}
#endif
++_new_count;
_new_size += size;
if (size < _min_size || _min_size == 0) {
_min_size = size;
}
//if (size > _max_size) {
if (size < 4192 && size > _max_size) {
_max_size = size;
}
void* p;
#if defined(RNS_USE_TLSF)
if (OS::_tlsf != nullptr) {
//TRACEF("--- allocating memory from tlsf (%u bytes)", size);
p = tlsf_malloc(OS::_tlsf, size);
//TRACEF("--- allocated memory from tlsf (%u bytes) (addr=%lx)", size, p);
}
else {
//TRACEF("--- allocating memory (%u bytes)", size);
p = malloc(size);
//TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p);
++_new_fault;
}
#else
//TRACEF("--- allocating memory (%u bytes)", size);
p = malloc(size);
//TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p);
#endif
return p;
}
// CBA Added attribute weak to avoid collision with new override on nrf52
void operator delete(void* p) {
//__attribute__((weak)) void operator delete(void* p) {
#if defined(RNS_USE_TLSF)
if (OS::_tlsf != nullptr) {
//TRACEF("--- freeing memory from tlsf (addr=%lx)", p);
tlsf_free(OS::_tlsf, p);
}
else {
//TRACEF("--- freeing memory (addr=%lx)", p);
free(p);
++_delete_fault;
}
#else
//TRACEF("--- freeing memory (addr=%lx)", p);
//TRACE("--- freeing memory");
free(p);
#endif
++_delete_count;
#if defined(RNS_USE_TLSF)
//if (_delete_count == _new_count) {
// TRACE("TLFS deinitializing");
// OS::dump_memory_stats();
// tlsf_destroy(OS::_tlsf);
// OS::_tlsf = nullptr;
//}
#endif
}
#if defined(RNS_USE_TLSF)
uint32_t _tlsf_used_count = 0;
uint32_t _tlsf_used_size = 0;
uint32_t _tlsf_free_count = 0;
uint32_t _tlsf_free_size = 0;
uint32_t _tlsf_free_max_size = 0;
void tlsf_mem_walker(void* ptr, size_t size, int used, void* user)
{
if (used) {
_tlsf_used_count++;
_tlsf_used_size += size;
}
else {
_tlsf_free_count++;
_tlsf_free_size += size;
if (size > _tlsf_free_max_size) {
_tlsf_free_max_size = size;
}
}
}
void dump_tlsf_stats() {
_tlsf_used_count = 0;
_tlsf_used_size = 0;
_tlsf_free_count = 0;
_tlsf_free_size = 0;
_tlsf_free_max_size = 0;
//TRACEF("TLSF Message: %s", _tlsf_msg);
if (OS::_tlsf == nullptr) {
return;
}
tlsf_walk_pool(tlsf_get_pool(OS::_tlsf), tlsf_mem_walker, nullptr);
HEAD("TLSF Stats", LOG_TRACE);
TRACEF("Buffer Size: %u", _buffer_size);
TRACEF("Contiguous Size: %u", _contiguous_size);
TRACEF("Used Count: %u", _tlsf_used_count);
TRACEF("Used Size: %u (%u%% used)", _tlsf_used_size, (unsigned)((double)_tlsf_used_size / (double)_buffer_size * 100.0));
TRACEF("Free Count: %u", _tlsf_free_count);
TRACEF("Free Size: %u (%u%% free)", _tlsf_free_size, (unsigned)((double)_tlsf_free_size / (double)_buffer_size * 100.0));
TRACEF("Max Free Size: %u (%u%% fragmented)\n", _tlsf_free_max_size, (unsigned)(100.0 - (double)_tlsf_free_max_size / (double)_tlsf_free_size * 100.0));
}
#endif
/*static*/ void OS::dump_allocator_stats() {
HEAD("Allocator Stats", LOG_TRACE);
TRACEF("New Count: %u", _new_count);
TRACEF("New Fault: %u", _new_fault);
TRACEF("Delete Count: %u", _delete_count);
TRACEF("Delete Fault: %u", _delete_fault);
TRACEF("Active Count: %u", (_new_count - _delete_count));
TRACEF("Min Size: %u", _min_size);
TRACEF("Max Size: %u", _max_size);
TRACEF("Avg Size: %u\n", (size_t)(_new_size / _new_count));
#if defined(RNS_USE_TLSF)
dump_tlsf_stats();
#endif
}
#endif // RNS_USE_ALLOCATOR
size_t maxContiguousAllocation() {
// Brute-force determine maximum allocation size
//const size_t block_size = 256;
const size_t block_size = 32;
size_t block_count;
for (block_count = 1; ; block_count++) {
void* ptr = malloc(block_count * block_size);
if (ptr == nullptr) {
break;
}
free(ptr);
}
return (block_count - 1) * block_size;
}
/*static*/ FileSystem OS::_filesystem = {Type::NONE};
/*static*/ uint64_t OS::_time_offset = 0;
/*static*/ size_t OS::heap_size() {
#if defined(ESP32)
return ESP.getHeapSize();
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
return dbgHeapTotal();
#else
return 0;
#endif
}
/*static*/ size_t OS::heap_available() {
#if defined(ESP32)
return ESP.getFreeHeap();
//return ESP.getMaxAllocHeap();
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
return dbgHeapFree();
#else
return 0;
#endif
}
/*static*/ void OS::dump_heap_stats() {
HEAD("Heap Stats", LOG_TRACE);
#if defined(ESP32)
TRACEF("Heap size: %u", ESP.getHeapSize());
TRACEF("Heap free: %u (%u%% free)", ESP.getFreeHeap(), (unsigned)((double)ESP.getFreeHeap() / (double)ESP.getHeapSize() * 100.0));
//TRACEF("Heap free: %u (%u%% free)", xPortGetFreeHeapSize(), (unsigned)((double)xPortGetFreeHeapSize() / (double)xPort * 100.0));
TRACEF("Heap min free: %u", ESP.getMinFreeHeap());
//TRACEF("Heap min free: %u", xPortGetMinimumEverFreeHeapSize());
TRACEF("Heap max alloc: %u (%u%% fragmented)", ESP.getMaxAllocHeap(), (unsigned)(100.0 - (double)ESP.getMaxAllocHeap() / (double)ESP.getFreeHeap() * 100.0));
//TRACEF("Heap max alloc: %u (%u%% fragmented)", ESP.getMaxAllocHeap(), (unsigned)(100.0 - (double)ESP.getMaxAllocHeap() / (double)xPortGetFreeHeapSize() * 100.0));
TRACEF("PSRAM size: %u", ESP.getPsramSize());
TRACEF("PSRAM free: %u (%u%% free)", ESP.getFreePsram(), (ESP.getPsramSize() > 0) ? (unsigned)((double)ESP.getFreePsram() / (double)ESP.getPsramSize() * 100.0) : 0);
TRACEF("PSRAM min free: %u", ESP.getMinFreePsram());
TRACEF("PSRAM max alloc: %u (%u%% fragmented)", ESP.getMaxAllocPsram(), (ESP.getFreePsram() > 0) ? (unsigned)(100.0 - (double)ESP.getMaxAllocPsram() / (double)ESP.getFreePsram() * 100.0) : 0);
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
if (loglevel() == LOG_TRACE) {
dbgMemInfo();
}
#endif
#if defined(RNS_USE_ALLOCATOR)
OS::dump_allocator_stats();
#endif
}

View File

@@ -0,0 +1,234 @@
#pragma once
#include "../FileSystem.h"
#include "../FileStream.h"
#include "../Bytes.h"
#include "tlsf.h"
#include <cmath>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <sys/time.h>
#ifdef ARDUINO
#include <Arduino.h>
#endif
#undef round
namespace RNS { namespace Utilities {
class OS {
private:
static FileSystem _filesystem;
static uint64_t _time_offset;
public:
static tlsf_t _tlsf;
public:
static inline uint64_t getTimeOffset() { return _time_offset; }
static inline void setTimeOffset(uint64_t offset) { _time_offset = offset; }
#ifdef ARDUINO
// return current time in milliseconds since startup
static inline uint64_t ltime() {
// handle roll-over of 32-bit millis (approx. 49 days)
static uint32_t low32, high32;
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return ((uint64_t)high32 << 32 | low32) + _time_offset;
}
#else
// return current time in milliseconds since 00:00:00, January 1, 1970 (Unix Epoch)
static inline uint64_t ltime() { timeval time; ::gettimeofday(&time, NULL); return (uint64_t)(time.tv_sec * 1000) + (uint64_t)(time.tv_usec / 1000); }
#endif
#ifdef ARDUINO
// return current time in float seconds since startup
static inline double time() { return (double)(ltime() / 1000.0); }
#else
// return current time in float seconds since 00:00:00, January 1, 1970 (Unix Epoch)
static inline double time() { timeval time; ::gettimeofday(&time, NULL); return (double)time.tv_sec + ((double)time.tv_usec / 1000000); }
#endif
// sleep for specified milliseconds
//static inline void sleep(float seconds) { ::sleep(seconds); }
#ifdef ARDUINO
static inline void sleep(float seconds) { delay((uint32_t)(seconds * 1000)); }
#else
static inline void sleep(float seconds) { timespec time; time.tv_sec = (time_t)(seconds); time.tv_nsec = (seconds - (float)time.tv_sec) * 1000000000; ::nanosleep(&time, nullptr); }
#endif
//static inline void sleep(uint32_t milliseconds) { ::sleep((float)milliseconds / 1000.0); }
// round decimal number to specified precision
//static inline float round(float value, uint8_t precision) { return std::round(value / precision) * precision; }
//static inline double round(double value, uint8_t precision) { return std::round(value / precision) * precision; }
static inline double round(double value, uint8_t precision) { return std::round(value / precision) * precision; }
static inline uint64_t from_bytes_big_endian(const uint8_t* data, size_t len) {
uint64_t result = 0;
for (size_t i = 0; i < len; ++i) {
result = (result << 8) | data[i];
}
return result;
}
// Detect endianness at runtime
static int is_big_endian(void) {
uint16_t test = 0x0102;
return ((uint8_t*)&test)[0] == 0x01;
}
// Byte swap functions
static uint16_t swap16(uint16_t val) {
return (val << 8) | (val >> 8);
}
static uint32_t swap32(uint32_t val) {
return ((val << 24) & 0xFF000000) |
((val << 8) & 0x00FF0000) |
((val >> 8) & 0x0000FF00) |
((val >> 24) & 0x000000FF);
}
// Platform-independent replacements
static uint16_t portable_htons(uint16_t val) {
return is_big_endian() ? val : swap16(val);
}
static uint32_t portable_htonl(uint32_t val) {
return is_big_endian() ? val : swap32(val);
}
static uint16_t portable_ntohs(uint16_t val) {
return is_big_endian() ? val : swap16(val);
}
static uint32_t portable_ntohl(uint32_t val) {
return is_big_endian() ? val : swap32(val);
}
#if defined(RNS_USE_ALLOCATOR)
static void dump_allocator_stats();
#endif
inline static void register_filesystem(FileSystem& filesystem) {
TRACE("Registering filesystem...");
_filesystem = filesystem;
}
/*
inline static void register_filesystem(FileSystemImpl* filesystemimpl) {
TRACE("Registering filesystem...");
_filesystem = filesystemimpl;
}
*/
inline static void deregister_filesystem() {
TRACE("Deregistering filesystem...");
_filesystem = {Type::NONE};
}
inline static FileSystem& get_filesystem() {
return _filesystem;
}
inline static bool file_exists(const char* file_path) {
if (!_filesystem) {
WARNING("file_exists: filesystem not registered");
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.file_exists(file_path);
}
inline static size_t read_file(const char* file_path, Bytes& data) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.read_file(file_path, data);
}
inline static size_t write_file(const char* file_path, const Bytes& data) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.write_file(file_path, data);
}
inline static FileStream open_file(const char* file_path, RNS::FileStream::MODE file_mode) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.open_file(file_path, file_mode);
}
inline static bool remove_file(const char* file_path) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.remove_file(file_path);
}
inline static bool rename_file(const char* from_file_path, const char* to_file_path) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.rename_file(from_file_path, to_file_path);
}
inline static bool directory_exists(const char* directory_path) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.directory_exists(directory_path);
}
inline static bool create_directory(const char* directory_path) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.create_directory(directory_path);
}
inline static bool remove_directory(const char* directory_path) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.remove_directory(directory_path);
}
inline static std::list<std::string> list_directory(const char* directory_path) {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.list_directory(directory_path);
}
inline static size_t storage_size() {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.storage_size();
}
inline static size_t storage_available() {
if (!_filesystem) {
throw std::runtime_error("FileSystem has not been registered");
}
return _filesystem.storage_available();
}
static size_t heap_size();
static size_t heap_available();
static void dump_heap_stats();
};
} }

View File

@@ -0,0 +1,9 @@
#include "Persistence.h"
#include "Bytes.h"
using namespace RNS;
/*static*/ //DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE);
/*static*/ JsonDocument _document;
/*static*/ Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE);

View File

@@ -0,0 +1,699 @@
#pragma once
// CBA NOTE If headers for classes referenced in this file are not included here,
// then they MUST be included BEFORE this header is included.
#include "Transport.h"
#include "Type.h"
#include <ArduinoJson.h>
#include <map>
#include <vector>
#include <set>
#include <string>
namespace ArduinoJson {
// ArduinoJSON serialization support for std::vector<T>
template <typename T>
struct Converter<std::vector<T>> {
static void toJson(const std::vector<T>& src, JsonVariant dst) {
TRACE("<<< Serializing vector");
JsonArray array = dst.to<JsonArray>();
for (T item : src)
array.add(item);
TRACE("<<< Finished serializing vector");
}
static std::vector<T> fromJson(JsonVariantConst src) {
TRACE("<<< Deserializing vector");
std::vector<T> dst;
for (T item : src.as<JsonArrayConst>())
dst.push_back(item);
TRACE("<<< Finished deserializing vector");
return dst;
}
static bool checkJson(JsonVariantConst src) {
JsonArrayConst array = src;
bool result = array;
for (JsonVariantConst item : array)
result &= item.is<T>();
return result;
}
};
// ArduinoJSON serialization support for std::set<T>
template <typename T>
struct Converter<std::set<T>> {
static void toJson(const std::set<T>& src, JsonVariant dst) {
JsonArray array = dst.to<JsonArray>();
for (T item : src)
array.add(item);
}
static std::set<T> fromJson(JsonVariantConst src) {
std::set<T> dst;
for (T item : src.as<JsonArrayConst>())
dst.insert(item);
return dst;
}
static bool checkJson(JsonVariantConst src) {
JsonArrayConst array = src;
bool result = array;
for (JsonVariantConst item : array)
result &= item.is<T>();
return result;
}
};
// ArduinoJSON serialization support for std::map<std::string, T>
template <typename T>
struct Converter<std::map<std::string, T>> {
static void toJson(const std::map<std::string, T>& src, JsonVariant dst) {
JsonObject obj = dst.to<JsonObject>();
for (const auto& item : src)
obj[item.first] = item.second;
}
static std::map<std::string, T> fromJson(JsonVariantConst src) {
std::map<std::string, T> dst;
for (JsonPairConst item : src.as<JsonObjectConst>())
dst[item.key().c_str()] = item.value().as<T>();
return dst;
}
static bool checkJson(JsonVariantConst src) {
JsonObjectConst obj = src;
bool result = obj;
for (JsonPairConst item : obj)
result &= item.value().is<T>();
return result;
}
};
// ArduinoJSON serialization support for std::map<Bytes, T>
template <typename T>
struct Converter<std::map<RNS::Bytes, T>> {
static void toJson(const std::map<RNS::Bytes, T>& src, JsonVariant dst) {
JsonObject obj = dst.to<JsonObject>();
for (const auto& item : src) {
//obj[item.first] = item.second;
obj[item.first.toHex()] = item.second;
}
}
static std::map<RNS::Bytes, T> fromJson(JsonVariantConst src) {
std::map<RNS::Bytes, T> dst;
for (JsonPairConst item : src.as<JsonObjectConst>()) {
//dst[item.key().c_str()] = item.value().as<T>();
RNS::Bytes key;
key.assignHex(item.key().c_str());
//dst[key] = item.value().as<T>();
dst.insert({key, item.value().as<T>()});
}
return dst;
}
static bool checkJson(JsonVariantConst src) {
JsonObjectConst obj = src;
bool result = obj;
for (JsonPairConst item : obj) {
result &= item.value().is<T>();
}
return result;
}
};
/*
// ArduinoJSON serialization support for RNS::Bytes
template <>
struct Converter<RNS::Bytes> {
static bool toJson(const RNS::Bytes& src, JsonVariant dst) {
//TRACE("<<< Serializing Bytes");
//TRACE("<<< Finished serializing Bytes");
return true;
}
static RNS::Bytes fromJson(JsonVariantConst src) {
Bytes bytes;
//TRACE(">>> Deserializing Bytes");
//TRACE(">>> Finished deserializing Bytes");
return bytes;
}
static bool checkJson(JsonVariantConst src) {
return
src.is<RNS::Bytes>();
}
};
*/
/*
// ArduinoJSON serialization support for RNS::Interface
template <>
struct Converter<RNS::Interface> {
static bool toJson(const RNS::Interface& src, JsonVariant dst) {
if (!src) {
return dst.set(nullptr);
}
//TRACE("<<< Serializing interface hash " + src.get_hash().toHex());
return dst.set(src.get_hash().toHex());
}
static RNS::Interface fromJson(JsonVariantConst src) {
if (!src.isNull()) {
RNS::Bytes hash;
hash.assignHex(src.as<const char*>());
//TRACE(">>> Deserialized interface hash " + hash.toHex());
//TRACE(">>> Querying transport for interface");
// Query transport for matching interface
return RNS::Transport::find_interface_from_hash(hash);
}
else {
return {RNS::Type::NONE};
}
}
static bool checkJson(JsonVariantConst src) {
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
}
};
*/
/*
// ArduinoJSON serialization support for RNS::Packet
template <>
struct Converter<RNS::Packet> {
static bool toJson(const RNS::Packet& src, JsonVariant dst) {
if (!src) {
return dst.set(nullptr);
}
//TRACE("<<< Serializing packet hash " + src.get_hash().toHex());
// Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
RNS::Transport::cache_packet(src, true);
return dst.set(src.get_hash().toHex());
}
static RNS::Packet fromJson(JsonVariantConst src) {
if (!src.isNull()) {
RNS::Bytes hash;
hash.assignHex(src.as<const char*>());
//TRACE(">>> Deserialized packet hash " + hash.toHex());
//TRACE(">>> Querying transport for cached packet");
// Query transport for matching packet
return RNS::Transport::get_cached_packet(hash);
}
else {
return {RNS::Type::NONE};
}
}
static bool checkJson(JsonVariantConst src) {
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
}
};
*/
// ArduinoJSON serialization support for RNS::Packet
template <>
struct Converter<RNS::Packet> {
static bool toJson(const RNS::Packet& src, JsonVariant dst) {
//TRACE("<<< Serializing Packet");
//dst["hash"] = src.get_hash();
dst["raw"] = src.raw();
dst["sent_at"] = src.sent_at();
dst["destination_hash"] = src.get_hash();
//TRACE("<<< Finished serializing Packet");
return true;
}
static RNS::Packet fromJson(JsonVariantConst src) {
//TRACE(">>> Deserializing Packet");
RNS::Bytes raw = src["raw"];
//RNS::Bytes raw = src["raw"].as<const RNS::Bytes&>();
RNS::Packet packet(RNS::Destination(RNS::Type::NONE), raw);
//packet.set_hash(src["hash"]);
packet.sent_at(src["sent_at"]);
RNS::Bytes destination_hash = src["destination_hash"];
// set cached flag since pcket was read from cache
packet.cached(true);
//TRACE(">>> Finished deserializing Packet");
return packet;
}
static bool checkJson(JsonVariantConst src) {
return
//src["hash"].is<RNS::Bytes>() &&
src["raw"].is<RNS::Bytes>() &&
src["sent_at"].is<double>() &&
src["destination_hash"].is<RNS::Bytes>();
}
};
// ArduinoJSON serialization support for RNS::Transport::PacketEntry
template <>
struct Converter<RNS::Transport::PacketEntry> {
static bool toJson(const RNS::Transport::PacketEntry& src, JsonVariant dst) {
//TRACE("<<< Serializing Transport::PacketEntry");
//dst["hash"] = src._hash;
dst["raw"] = src._raw;
dst["sent_at"] = src._sent_at;
dst["destination_hash"] = src._destination_hash;
//TRACE("<<< Finished serializing Transport::PacketEntry");
return true;
}
static RNS::Transport::PacketEntry fromJson(JsonVariantConst src) {
//TRACE(">>> Deserializing Transport::PacketEntry");
RNS::Transport::PacketEntry dst;
//dst._hash = src["hash"];
dst._raw = src["raw"];
dst._sent_at = src["sent_at"];
dst._destination_hash = src["destination_hash"];
// set cached flag since pcket was read from cache
dst._cached = true;
//TRACE(">>> Finished deserializing Transport::PacketEntry");
return dst;
}
static bool checkJson(JsonVariantConst src) {
return
//src["hash"].is<RNS::Bytes>() &&
src["raw"].is<RNS::Bytes>() &&
src["sent_at"].is<double>() &&
src["destination_hash"].is<RNS::Bytes>();
}
};
#if 1
// ArduinoJSON serialization support for RNS::Transport::DestinationEntry
template <>
struct Converter<RNS::Transport::DestinationEntry> {
static bool toJson(const RNS::Transport::DestinationEntry& src, JsonVariant dst) {
//TRACE("<<< Serializing Transport::DestinationEntry");
dst["timestamp"] = src._timestamp;
dst["received_from"] = src._received_from;
dst["announce_hops"] = src._hops;
dst["expires"] = src._expires;
dst["random_blobs"] = src._random_blobs;
/*
//dst["interface_hash"] = src._receiving_interface;
if (src._receiving_interface) {
dst["interface_hash"] = src._receiving_interface.get_hash();
}
else {
dst["interface_hash"] = nullptr;
}
// CBA TODO Move packet serialization to *after* destination table serialization since packets are useless
// anyway if there's no space to left to write destination table.
// Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
//dst["packet"] = src._announce_packet;
if (src._announce_packet) {
dst["packet_hash"] = src._announce_packet.get_hash();
// Only cache packet if not already cached
if (!src._announce_packet.cached()) {
if (RNS::Transport::cache_packet(src._announce_packet, true)) {
const_cast<RNS::Packet&>(src._announce_packet).cached(true);
}
}
else {
TRACE("Destination announce packet " + src._announce_packet.get_hash().toHex() + " is already cached");
}
}
else {
dst["packet_hash"] = nullptr;
}
*/
dst["interface_hash"] = src._receiving_interface;
dst["packet_hash"] = src._announce_packet;
//TRACE("<<< Finished Serializing Transport::DestinationEntry");
return true;
}
static RNS::Transport::DestinationEntry fromJson(JsonVariantConst src) {
//TRACE(">>> Deserializing Transport::DestinationEntry");
RNS::Transport::DestinationEntry dst;
dst._timestamp = src["timestamp"];
dst._received_from = src["received_from"];
dst._hops = src["announce_hops"];
dst._expires = src["expires"];
dst._random_blobs = src["random_blobs"].as<std::set<RNS::Bytes>>();
/*
//dst._receiving_interface = src["interface_hash"];
RNS::Bytes interface_hash = src["interface_hash"];
if (interface_hash) {
// Query transport for matching interface
dst._receiving_interface = RNS::Transport::find_interface_from_hash(interface_hash);
}
//dst._announce_packet = src["packet"];
RNS::Bytes packet_hash = src["packet_hash"];
if (packet_hash) {
// Query transport for matching packet
dst._announce_packet = RNS::Transport::get_cached_packet(packet_hash);
}
*/
dst._receiving_interface = src["interface_hash"];
dst._announce_packet = src["packet_hash"];
/*
//RNS::Transport::DestinationEntry dst(src["timestamp"], src["received_from"], src["announce_hops"], src["expires"], src["random_blobs"], src["receiving_interface"], src["packet"]);
RNS::Transport::DestinationEntry dst(
src["timestamp"].as<double>(),
src["received_from"].as<RNS::Bytes>(),
src["announce_hops"].as<int>(),
src["expires"].as<double>(),
src["random_blobs"].as<std::set<RNS::Bytes>>(),
src["receiving_interface"].as<RNS::Interface>(),
src["packet"].as<RNS::Packet>()
);
*/
//TRACE(">>> Finished Deserializing Transport::DestinationEntry");
return dst;
}
static bool checkJson(JsonVariantConst src) {
return
src["timestamp"].is<double>() &&
src["received_from"].is<RNS::Bytes>() &&
src["announce_hops"].is<int>() &&
src["expires"].is<double>() &&
src["random_blobs"].is<std::set<RNS::Bytes>>() &&
src["interface_hash"].is<RNS::Bytes>() &&
src["packet_hash"].is<RNS::Bytes>();
}
};
#endif
}
#if 0
namespace RNS {
inline bool convertToJson(const Transport::DestinationEntry& src, JsonVariant dst) {
//TRACE("<<< NEW Serializing Transport::DestinationEntry");
dst["timestamp"] = src._timestamp;
dst["received_from"] = src._received_from;
dst["announce_hops"] = src._hops;
dst["expires"] = src._expires;
dst["random_blobs"] = src._random_blobs;
/*
//dst["interface_hash"] = src._receiving_interface;
if (src._receiving_interface) {
dst["interface_hash"] = src._receiving_interface.get_hash();
}
else {
dst["interface_hash"] = nullptr;
}
// CBA TODO Move packet serialization to *after* destination table serialization since packets are useless
// anyway if there's no space to left to write destination table.
// Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
//dst["packet"] = src._announce_packet;
if (src._announce_packet) {
dst["packet_hash"] = src._announce_packet.get_hash();
// Only cache packet if not already cached
if (!src._announce_packet.cached()) {
if (RNS::Transport::cache_packet(src._announce_packet, true)) {
const_cast<RNS::Packet&>(src._announce_packet).cached(true);
}
}
else {
TRACE("Destination announce packet " + src._announce_packet.get_hash().toHex() + " is already cached");
}
}
else {
dst["packet_hash"] = nullptr;
}
*/
dst["interface_hash"] = src._receiving_interface;
dst["packet_hash"] = src._announce_packet;
//TRACE("<<< Finished Serializing Transport::DestinationEntry");
return true;
}
inline void convertFromJson(JsonVariantConst src, Transport::DestinationEntry& dst) {
//TRACE(">>> NEW Deserializing Transport::DestinationEntry");
dst._timestamp = src["timestamp"];
dst._received_from = src["received_from"];
dst._hops = src["announce_hops"];
dst._expires = src["expires"];
dst._random_blobs = src["random_blobs"].as<std::set<RNS::Bytes>>();
/*
//dst._receiving_interface = src["interface_hash"];
RNS::Bytes interface_hash = src["interface_hash"];
if (interface_hash) {
// Query transport for matching interface
dst._receiving_interface = RNS::Transport::find_interface_from_hash(interface_hash);
}
//dst._announce_packet = src["packet"];
RNS::Bytes packet_hash = src["packet_hash"];
if (packet_hash) {
// Query transport for matching packet
dst._announce_packet = RNS::Transport::get_cached_packet(packet_hash);
}
*/
dst._receiving_interface = src["interface_hash"];
dst._announce_packet = src["packet_hash"];
/*
//RNS::Transport::DestinationEntry dst(src["timestamp"], src["received_from"], src["announce_hops"], src["expires"], src["random_blobs"], src["receiving_interface"], src["packet"]);
RNS::Transport::DestinationEntry dst(
src["timestamp"].as<double>(),
src["received_from"].as<RNS::Bytes>(),
src["announce_hops"].as<int>(),
src["expires"].as<double>(),
src["random_blobs"].as<std::set<RNS::Bytes>>(),
src["receiving_interface"].as<RNS::Interface>(),
src["packet"].as<RNS::Packet>()
);
*/
//TRACE(">>> Finished Deserializing Transport::DestinationEntry");
}
inline bool canConvertFromJson(JsonVariantConst src, const Transport::DestinationEntry&) {
TRACE("=== NEW Checking Transport::DestinationEntry");
return
src["timestamp"].is<double>() &&
src["received_from"].is<RNS::Bytes>() &&
src["announce_hops"].is<int>() &&
src["expires"].is<double>() &&
src["random_blobs"].is<std::set<RNS::Bytes>>() &&
src["interface_hash"].is<RNS::Bytes>() &&
src["packet_hash"].is<RNS::Bytes>();
}
}
#endif
namespace RNS { namespace Persistence {
//static DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE);
static JsonDocument _document;
static Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE);
template <typename T> size_t crc(const T& obj) {
//TRACE("Persistence::crc<T>");
_document.set(obj);
size_t size = _buffer.capacity();
#ifdef USE_MSGPACK
size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
#else
size_t length = serializeJson(_document, _buffer.writable(size), size);
#endif
if (length < size) {
_buffer.resize(length);
}
TRACEF("Persistence::crc: serialized %d bytes", length);
return Utilities::Crc::crc32(0, _buffer.data(), _buffer.size());
}
template <typename T> size_t serialize(const T& obj, const char* file_path) {
//TRACE("Persistence::serialize<T>");
_document.set(obj);
size_t size = _buffer.capacity();
#ifdef USE_MSGPACK
size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
#else
size_t length = serializeJson(_document, _buffer.writable(size), size);
#endif
if (length < size) {
_buffer.resize(length);
}
TRACEF("Persistence::serialize: serialized %d bytes", length);
size_t wrote = 0;
if (length > 0) {
wrote = RNS::Utilities::OS::write_file(file_path, _buffer);
if (wrote == _buffer.size()) {
TRACEF("Persistence::serialize: wrote %d bytes", _buffer.size());
}
else {
TRACE("Persistence::serialize: write failed");
return 0;
}
}
else {
TRACE("Persistence::serialize: failed to serialize");
return 0;
}
return wrote;
}
template <typename T> size_t deserialize(T& obj, const char* file_path) {
//TRACE("Persistence::deserialize<T>");
size_t read = RNS::Utilities::OS::read_file(file_path, _buffer);
if (read > 0) {
TRACEF("Persistence::deserialize: read: %d bytes", _buffer.size());
//TRACE("testDeserializeVector: data: " + _buffer.toString());
#ifdef USE_MSGPACK
DeserializationError error = deserializeMsgPack(_document, _buffer.data());
#else
DeserializationError error = deserializeJson(_document, _buffer.data());
#endif
if (!error) {
TRACE("Persistence::deserialize: successfully deserialized document");
obj = _document.as<T>();
// CBA Following obj check doesn't work when T is a collection
//if (obj) {
return read;
//}
TRACE("Persistence::deserialize: failed to compose object");
}
else {
TRACE("Persistence::deserialize: failed to deserialize");
}
}
else {
TRACE("Persistence::deserialize: read failed");
}
return 0;
}
#if 1
template <typename T> size_t crc(std::map<Bytes, T>& map) {
//TRACE("Persistence::crc<map<Bytes, T>>");
uint32_t crc = 0;
crc = Utilities::Crc::crc32(crc, '{');
for (const auto& [key, value] : map) {
crc = Utilities::Crc::crc32(crc, '"');
std::string hex = key.toHex();
crc = Utilities::Crc::crc32(crc, hex.c_str());
crc = Utilities::Crc::crc32(crc, '"');
crc = Utilities::Crc::crc32(crc, ':');
_document.set(value);
size_t size = _buffer.capacity();
#ifdef USE_MSGPACK
size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
#else
size_t length = serializeJson(_document, _buffer.writable(size), size);
#endif
if (length < size) {
_buffer.resize(length);
}
TRACEF("Persistence::crc: serialized entry %d bytes", length);
if (length > 0) {
crc = Utilities::Crc::crc32(crc, _buffer.data(), _buffer.size());
}
else {
// if failed to serialize entry then write empty entry
crc = Utilities::Crc::crc32(crc, "{}");
}
crc = Utilities::Crc::crc32(crc, ',');
}
return Utilities::Crc::crc32(crc, '}');
}
template <typename T> size_t serialize(std::map<Bytes, T>& map, const char* file_path, uint32_t& crc) {
//TRACE("Persistence::serialize<map<Bytes,T>>");
// CBA TODO: Use stream here instead to avoid having to buffer entire structure
RNS::FileStream stream = RNS::Utilities::OS::open_file(file_path, RNS::FileStream::MODE_WRITE);
if (!stream) {
TRACE("Persistence::serialize: failed to open write stream");
return 0;
}
stream.write('{');
for (const auto& [key, value] : map) {
stream.write('"');
std::string hex = key.toHex();
stream.write(hex.c_str());
stream.write('"');
stream.write(':');
_document.set(value);
#ifdef USE_MSGPACK
size_t length = serializeMsgPack(_document, stream);
#else
size_t length = serializeJson(_document, stream);
#endif
TRACEF("Persistence::serialize: serialized entry %d bytes", length);
if (length == 0) {
// if failed to serialize entry then write empty entry
stream.write("{}");
}
stream.write(',');
}
stream.write('}');
TRACEF("Persistence::serialize: stream size: %d bytes", stream.size());
crc = stream.crc();
return stream.size();
}
template <typename T> size_t serialize(std::map<Bytes, T>& map, const char* file_path) {
uint32_t crc;
return serialize(map, file_path, crc);
}
template <typename T> size_t deserialize(std::map<Bytes, T>& map, const char* file_path, uint32_t& crc) {
//TRACE("Persistence::deserialize<map<Bytes,T>>");
// CBA TODO: Use stream here instead to avoid having to buffer entire structure
RNS::FileStream stream = RNS::Utilities::OS::open_file(file_path, RNS::FileStream::MODE_READ);
if (!stream) {
TRACE("Persistence::deserialize: failed to open read stream");
return 0;
}
TRACEF("Persistence::deserialize: size: %d bytes", stream.size());
map.clear();
if (stream.size() == 0) {
TRACE("Persistence::deserialize: read stream is empty");
return 0;
}
// find opening brace
if (stream.find('{')) {
char key_str[RNS::Type::Reticulum::DESTINATION_LENGTH*2+1] = "";
do {
key_str[0] = 0;
// find map key opening quote
if (stream.find('"')) {
if (stream.readBytesUntil('"', key_str, sizeof(key_str)) > 0) {
Bytes key;
key.assignHex(key_str);
TRACEF("Persistence::deserialize: key: %s", key.toHex().c_str());
if (stream.find(':')) {
#ifdef USE_MSGPACK
//DeserializationError error = deserializeMsgPack(_document, _buffer.data());
DeserializationError error = deserializeMsgPack(_document, stream);
#else
//DeserializationError error = deserializeJson(_document, _buffer.data());
DeserializationError error = deserializeJson(_document, stream);
#endif
if (!error) {
TRACE("Persistence::deserialize: successfully deserialized entry");
T obj = _document.as<T>();
// CBA Following obj check doesn't work when T is a collection
//if (obj) {
// TRACE("Persistence::deserialize: failed to compose object");
// break;
//}
map.insert({key, obj});
}
else {
TRACE("Persistence::deserialize: failed to deserialize entry");
//break;
}
if (!stream.find(',')) {
break;
}
}
}
}
} while (key_str[0] != 0);
}
crc = stream.crc();
return stream.size();
}
template <typename T> size_t deserialize(std::map<Bytes, T>& map, const char* file_path) {
uint32_t crc;
return deserialize(map, file_path, crc);
}
#endif
} }

View File

@@ -0,0 +1,354 @@
/*
Print.cpp - Base class that provides print() and println()
Copyright (c) 2008 David A. Mellis. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 23 November 2006 by David A. Mellis
Modified December 2014 by Ivan Grokhotkov
Modified May 2015 by Michael C. Miller - ESP31B progmem support
*/
#ifndef ARDUINO
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include "Print.h"
extern "C" {
#include "time.h"
}
// Public Methods //////////////////////////////////////////////////////////////
/* default implementation: may be overridden */
size_t Print::write(const uint8_t *buffer, size_t size)
{
size_t n = 0;
while(size--) {
n += write(*buffer++);
}
return n;
}
size_t Print::printf(const char *format, ...)
{
char loc_buf[64];
char * temp = loc_buf;
va_list arg;
va_list copy;
va_start(arg, format);
va_copy(copy, arg);
int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
va_end(copy);
if(len < 0) {
va_end(arg);
return 0;
}
if(len >= (int)sizeof(loc_buf)){ // comparation of same sign type for the compiler
temp = (char*) malloc(len+1);
if(temp == NULL) {
va_end(arg);
return 0;
}
len = vsnprintf(temp, len+1, format, arg);
}
va_end(arg);
len = write((uint8_t*)temp, len);
if(temp != loc_buf){
free(temp);
}
return len;
}
size_t Print::print(const std::string &s)
{
return write(s.c_str(), s.length());
}
size_t Print::print(const char str[])
{
return write(str);
}
size_t Print::print(char c)
{
return write(c);
}
size_t Print::print(unsigned char b, int base)
{
return print((unsigned long) b, base);
}
size_t Print::print(int n, int base)
{
return print((long) n, base);
}
size_t Print::print(unsigned int n, int base)
{
return print((unsigned long) n, base);
}
size_t Print::print(long n, int base)
{
int t = 0;
if (base == 10 && n < 0) {
t = print('-');
n = -n;
}
return printNumber(static_cast<unsigned long>(n), base) + t;
}
size_t Print::print(unsigned long n, int base)
{
if(base == 0) {
return write(n);
} else {
return printNumber(n, base);
}
}
size_t Print::print(long long n, int base)
{
int t = 0;
if (base == 10 && n < 0) {
t = print('-');
n = -n;
}
return printNumber(static_cast<unsigned long long>(n), base) + t;
}
size_t Print::print(unsigned long long n, int base)
{
if (base == 0) {
return write(n);
} else {
return printNumber(n, base);
}
}
size_t Print::print(double n, int digits)
{
return printFloat(n, digits);
}
size_t Print::print(struct tm * timeinfo, const char * format)
{
const char * f = format;
if(!f){
f = "%c";
}
char buf[64];
size_t written = strftime(buf, 64, f, timeinfo);
if(written == 0){
return written;
}
return print(buf);
}
size_t Print::println(void)
{
return print("\r\n");
}
size_t Print::println(const std::string &s)
{
size_t n = print(s);
n += println();
return n;
}
size_t Print::println(const char c[])
{
size_t n = print(c);
n += println();
return n;
}
size_t Print::println(char c)
{
size_t n = print(c);
n += println();
return n;
}
size_t Print::println(unsigned char b, int base)
{
size_t n = print(b, base);
n += println();
return n;
}
size_t Print::println(int num, int base)
{
size_t n = print(num, base);
n += println();
return n;
}
size_t Print::println(unsigned int num, int base)
{
size_t n = print(num, base);
n += println();
return n;
}
size_t Print::println(long num, int base)
{
size_t n = print(num, base);
n += println();
return n;
}
size_t Print::println(unsigned long num, int base)
{
size_t n = print(num, base);
n += println();
return n;
}
size_t Print::println(long long num, int base)
{
size_t n = print(num, base);
n += println();
return n;
}
size_t Print::println(unsigned long long num, int base)
{
size_t n = print(num, base);
n += println();
return n;
}
size_t Print::println(double num, int digits)
{
size_t n = print(num, digits);
n += println();
return n;
}
size_t Print::println(struct tm * timeinfo, const char * format)
{
size_t n = print(timeinfo, format);
n += println();
return n;
}
// Private Methods /////////////////////////////////////////////////////////////
size_t Print::printNumber(unsigned long n, uint8_t base)
{
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
char *str = &buf[sizeof(buf) - 1];
*str = '\0';
// prevent crash if called with base == 1
if(base < 2) {
base = 10;
}
do {
char c = n % base;
n /= base;
*--str = c < 10 ? c + '0' : c + 'A' - 10;
} while (n);
return write(str);
}
size_t Print::printNumber(unsigned long long n, uint8_t base)
{
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
char* str = &buf[sizeof(buf) - 1];
*str = '\0';
// prevent crash if called with base == 1
if (base < 2) {
base = 10;
}
do {
auto m = n;
n /= base;
char c = m - base * n;
*--str = c < 10 ? c + '0' : c + 'A' - 10;
} while (n);
return write(str);
}
size_t Print::printFloat(double number, uint8_t digits)
{
size_t n = 0;
if(isnan(number)) {
return print("nan");
}
if(isinf(number)) {
return print("inf");
}
if(number > 4294967040.0) {
return print("ovf"); // constant determined empirically
}
if(number < -4294967040.0) {
return print("ovf"); // constant determined empirically
}
// Handle negative numbers
if(number < 0.0) {
n += print('-');
number = -number;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
for(uint8_t i = 0; i < digits; ++i) {
rounding /= 10.0;
}
number += rounding;
// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long) number;
double remainder = number - (double) int_part;
n += print(int_part);
// Print the decimal point, but only if there are digits beyond
if(digits > 0) {
n += print(".");
}
// Extract digits from the remainder one at a time
while(digits-- > 0) {
remainder *= 10.0;
int toPrint = int(remainder);
n += print(toPrint);
remainder -= toPrint;
}
return n;
}
#endif

View File

@@ -0,0 +1,121 @@
/*
Print.h - Base class that provides print() and println()
Copyright (c) 2008 David A. Mellis. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ARDUINO
#ifndef Print_h
#define Print_h
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <string>
//#define DEC 10
//#define HEX 16
//#define OCT 8
//#define BIN 2
static constexpr uint8_t DEC {10};
static constexpr uint8_t HEX {16};
static constexpr uint8_t OCT {8};
static constexpr uint8_t BIN {2};
class Print
{
private:
int write_error;
size_t printNumber(unsigned long, uint8_t);
size_t printNumber(unsigned long long, uint8_t);
size_t printFloat(double, uint8_t);
protected:
void setWriteError(int err = 1)
{
write_error = err;
}
public:
Print() :
write_error(0)
{
}
virtual ~Print() {}
int getWriteError()
{
return write_error;
}
void clearWriteError()
{
setWriteError(0);
}
virtual size_t write(uint8_t) = 0;
size_t write(const char *str)
{
if(str == NULL) {
return 0;
}
return write((const uint8_t *) str, strlen(str));
}
virtual size_t write(const uint8_t *buffer, size_t size);
size_t write(const char *buffer, size_t size)
{
return write((const uint8_t *) buffer, size);
}
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
// add availableForWrite to make compatible with Arduino Print.h
// default to zero, meaning "a single write may block"
// should be overriden by subclasses with buffering
virtual int availableForWrite() { return 0; }
size_t print(const std::string &);
size_t print(const char[]);
size_t print(char);
size_t print(unsigned char, int = DEC);
size_t print(int, int = DEC);
size_t print(unsigned int, int = DEC);
size_t print(long, int = DEC);
size_t print(unsigned long, int = DEC);
size_t print(long long, int = DEC);
size_t print(unsigned long long, int = DEC);
size_t print(double, int = 2);
size_t print(struct tm * timeinfo, const char * format = NULL);
size_t println(const std::string &s);
size_t println(const char[]);
size_t println(char);
size_t println(unsigned char, int = DEC);
size_t println(int, int = DEC);
size_t println(unsigned int, int = DEC);
size_t println(long, int = DEC);
size_t println(unsigned long, int = DEC);
size_t println(long long, int = DEC);
size_t println(unsigned long long, int = DEC);
size_t println(double, int = 2);
size_t println(struct tm * timeinfo, const char * format = NULL);
size_t println(void);
virtual void flush() { /* Empty implementation for backward compatibility */ }
};
#endif
#endif

View File

@@ -0,0 +1,339 @@
/*
Stream.cpp - adds parsing methods to Stream class
Copyright (c) 2008 David A. Mellis. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Created July 2011
parsing functions based on TextFinder library by Michael Margolis
*/
#ifndef ARDUINO
#include "Stream.h"
#include "OS.h"
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
// private method to read stream with timeout
int Stream::timedRead()
{
int c;
_startMillis = RNS::Utilities::OS::ltime();
do {
c = read();
if(c >= 0) {
return c;
}
} while(RNS::Utilities::OS::ltime() - _startMillis < _timeout);
return -1; // -1 indicates timeout
}
// private method to peek stream with timeout
int Stream::timedPeek()
{
int c;
_startMillis = RNS::Utilities::OS::ltime();
do {
c = peek();
if(c >= 0) {
return c;
}
} while(RNS::Utilities::OS::ltime() - _startMillis < _timeout);
return -1; // -1 indicates timeout
}
// returns peek of the next digit in the stream or -1 if timeout
// discards non-numeric characters
int Stream::peekNextDigit()
{
int c;
while(1) {
c = timedPeek();
if(c < 0) {
return c; // timeout
}
if(c == '-') {
return c;
}
if(c >= '0' && c <= '9') {
return c;
}
read(); // discard non-numeric
}
}
// Public Methods
//////////////////////////////////////////////////////////////
void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait
{
_timeout = timeout;
}
unsigned long Stream::getTimeout(void) {
return _timeout;
}
// find returns true if the target string is found
bool Stream::find(const char *target)
{
return findUntil(target, strlen(target), NULL, 0);
}
// reads data from the stream until the target string of given length is found
// returns true if target string is found, false if timed out
bool Stream::find(const char *target, size_t length)
{
return findUntil(target, length, NULL, 0);
}
// as find but search ends if the terminator string is found
bool Stream::findUntil(const char *target, const char *terminator)
{
return findUntil(target, strlen(target), terminator, strlen(terminator));
}
// reads data from the stream until the target string of the given length is found
// search terminated if the terminator string is found
// returns true if target string is found, false if terminated or timed out
bool Stream::findUntil(const char *target, size_t targetLen, const char *terminator, size_t termLen)
{
if (terminator == NULL) {
MultiTarget t[1] = {{target, targetLen, 0}};
return findMulti(t, 1) == 0 ? true : false;
} else {
MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}};
return findMulti(t, 2) == 0 ? true : false;
}
}
int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) {
// any zero length target string automatically matches and would make
// a mess of the rest of the algorithm.
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
if (t->len <= 0)
return t - targets;
}
while (1) {
int c = timedRead();
if (c < 0)
return -1;
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
// the simple case is if we match, deal with that first.
if (c == t->str[t->index]) {
if (++t->index == t->len)
return t - targets;
else
continue;
}
// if not we need to walk back and see if we could have matched further
// down the stream (ie '1112' doesn't match the first position in '11112'
// but it will match the second position so we can't just reset the current
// index to 0 when we find a mismatch.
if (t->index == 0)
continue;
int origIndex = t->index;
do {
--t->index;
// first check if current char works against the new current index
if (c != t->str[t->index])
continue;
// if it's the only char then we're good, nothing more to check
if (t->index == 0) {
t->index++;
break;
}
// otherwise we need to check the rest of the found string
int diff = origIndex - t->index;
size_t i;
for (i = 0; i < t->index; ++i) {
if (t->str[i] != t->str[i + diff])
break;
}
// if we successfully got through the previous loop then our current
// index is good.
if (i == t->index) {
t->index++;
break;
}
// otherwise we just try the next index
} while (t->index);
}
}
// unreachable
return -1;
}
// returns the first valid (long) integer value from the current position.
// initial characters that are not digits (or the minus sign) are skipped
// function is terminated by the first character that is not a digit.
long Stream::parseInt()
{
return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout)
}
// as above but a given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored
long Stream::parseInt(char skipChar)
{
bool isNegative = false;
long value = 0;
int c;
c = peekNextDigit();
// ignore non numeric leading characters
if(c < 0) {
return 0; // zero returned if timeout
}
do {
if(c == skipChar) {
} // ignore this charactor
else if(c == '-') {
isNegative = true;
} else if(c >= '0' && c <= '9') { // is c a digit?
value = value * 10 + c - '0';
}
read(); // consume the character we got with peek
c = timedPeek();
} while((c >= '0' && c <= '9') || c == skipChar);
if(isNegative) {
value = -value;
}
return value;
}
// as parseInt but returns a floating point value
float Stream::parseFloat()
{
return parseFloat(NO_SKIP_CHAR);
}
// as above but the given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored
float Stream::parseFloat(char skipChar)
{
bool isNegative = false;
bool isFraction = false;
long value = 0;
int c;
float fraction = 1.0;
c = peekNextDigit();
// ignore non numeric leading characters
if(c < 0) {
return 0; // zero returned if timeout
}
do {
if(c == skipChar) {
} // ignore
else if(c == '-') {
isNegative = true;
} else if(c == '.') {
isFraction = true;
} else if(c >= '0' && c <= '9') { // is c a digit?
value = value * 10 + c - '0';
if(isFraction) {
fraction *= 0.1f;
}
}
read(); // consume the character we got with peek
c = timedPeek();
} while((c >= '0' && c <= '9') || c == '.' || c == skipChar);
if(isNegative) {
value = -value;
}
if(isFraction) {
return value * fraction;
} else {
return value;
}
}
// read characters from stream into buffer
// terminates if length characters have been read, or timeout (see setTimeout)
// returns the number of characters placed in the buffer
// the buffer is NOT null terminated.
//
size_t Stream::readBytes(char *buffer, size_t length)
{
size_t count = 0;
while(count < length) {
int c = timedRead();
if(c < 0) {
break;
}
*buffer++ = (char) c;
count++;
}
return count;
}
// as readBytes with terminator character
// terminates if length characters have been read, timeout, or if the terminator character detected
// returns the number of characters placed in the buffer (0 means no valid data found)
size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)
{
if(length < 1) {
return 0;
}
size_t index = 0;
while(index < length) {
int c = timedRead();
if(c < 0 || c == terminator) {
break;
}
*buffer++ = (char) c;
index++;
}
return index; // return number of characters, not including null terminator
}
std::string Stream::readString()
{
std::string ret;
int c = timedRead();
while(c >= 0) {
ret += (char) c;
c = timedRead();
}
return ret;
}
std::string Stream::readStringUntil(char terminator)
{
std::string ret;
int c = timedRead();
while(c >= 0 && c != terminator) {
ret += (char) c;
c = timedRead();
}
return ret;
}
#endif

View File

@@ -0,0 +1,143 @@
/*
Stream.h - base class for character-based streams.
Copyright (c) 2010 David A. Mellis. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
parsing functions based on TextFinder library by Michael Margolis
*/
#ifndef ARDUINO
#ifndef Stream_h
#define Stream_h
#include <inttypes.h>
#include "Print.h"
// compatability macros for testing
/*
#define getInt() parseInt()
#define getInt(skipChar) parseInt(skipchar)
#define getFloat() parseFloat()
#define getFloat(skipChar) parseFloat(skipChar)
#define getString( pre_string, post_string, buffer, length)
readBytesBetween( pre_string, terminator, buffer, length)
*/
class Stream: public Print
{
protected:
unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
unsigned long _startMillis; // used for timeout measurement
int timedRead(); // private method to read stream with timeout
int timedPeek(); // private method to peek stream with timeout
int peekNextDigit(); // returns the next numeric digit in the stream or -1 if timeout
public:
virtual int available() = 0;
virtual int read() = 0;
virtual int peek() = 0;
Stream():_startMillis(0)
{
_timeout = 1000;
}
virtual ~Stream() {}
// parsing methods
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
unsigned long getTimeout(void);
bool find(const char *target); // reads data from the stream until the target string is found
bool find(uint8_t *target)
{
return find((char *) target);
}
// returns true if target string is found, false if timed out (see setTimeout)
bool find(const char *target, size_t length); // reads data from the stream until the target string of given length is found
bool find(const uint8_t *target, size_t length)
{
return find((char *) target, length);
}
// returns true if target string is found, false if timed out
bool find(char target)
{
return find (&target, 1);
}
bool findUntil(const char *target, const char *terminator); // as find but search ends if the terminator string is found
bool findUntil(const uint8_t *target, const char *terminator)
{
return findUntil((char *) target, terminator);
}
bool findUntil(const char *target, size_t targetLen, const char *terminate, size_t termLen); // as above but search ends if the terminate string is found
bool findUntil(const uint8_t *target, size_t targetLen, const char *terminate, size_t termLen)
{
return findUntil((char *) target, targetLen, terminate, termLen);
}
long parseInt(); // returns the first valid (long) integer value from the current position.
// initial characters that are not digits (or the minus sign) are skipped
// integer is terminated by the first character that is not a digit.
float parseFloat(); // float version of parseInt
virtual size_t readBytes(char *buffer, size_t length); // read chars from stream into buffer
virtual size_t readBytes(uint8_t *buffer, size_t length)
{
return readBytes((char *) buffer, length);
}
// terminates if length characters have been read or timeout (see setTimeout)
// returns the number of characters placed in the buffer (0 means no valid data found)
size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character
size_t readBytesUntil(char terminator, uint8_t *buffer, size_t length)
{
return readBytesUntil(terminator, (char *) buffer, length);
}
// terminates if length characters have been read, timeout, or if the terminator character detected
// returns the number of characters placed in the buffer (0 means no valid data found)
// Arduino String functions to be added here
virtual std::string readString();
std::string readStringUntil(char terminator);
protected:
long parseInt(char skipChar); // as above but the given skipChar is ignored
// as above but the given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored
float parseFloat(char skipChar); // as above but the given skipChar is ignored
struct MultiTarget {
const char *str; // string you're searching for
size_t len; // length of string you're searching for
size_t index; // index used by the search routine.
};
// This allows you to search for an arbitrary number of strings.
// Returns index of the target that is found first or -1 if timeout occurs.
int findMulti(struct MultiTarget *targets, int tCount);
};
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
#ifndef INCLUDED_tlsf
#define INCLUDED_tlsf
/*
** Two Level Segregated Fit memory allocator, version 3.1.
** Written by Matthew Conte
** http://tlsf.baisoku.org
**
** Based on the original documentation by Miguel Masmano:
** http://www.gii.upv.es/tlsf/main/docs
**
** This implementation was written to the specification
** of the document, therefore no GPL restrictions apply.
**
** Copyright (c) 2006-2016, Matthew Conte
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stddef.h>
#if defined(__cplusplus)
extern "C" {
#endif
/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
/* pool_t: a block of memory that TLSF can manage. */
typedef void* tlsf_t;
typedef void* pool_t;
/* Create/destroy a memory pool. */
tlsf_t tlsf_create(void* mem);
tlsf_t tlsf_create_with_pool(void* mem, size_t bytes);
void tlsf_destroy(tlsf_t tlsf);
pool_t tlsf_get_pool(tlsf_t tlsf);
/* Add/remove memory pools. */
pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
/* malloc/memalign/realloc/free replacements. */
void* tlsf_malloc(tlsf_t tlsf, size_t bytes);
void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t bytes);
void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
void tlsf_free(tlsf_t tlsf, void* ptr);
/* Returns internal block size, not original request size */
size_t tlsf_block_size(void* ptr);
/* Overheads/limits of internal structures. */
size_t tlsf_size(void);
size_t tlsf_align_size(void);
size_t tlsf_block_size_min(void);
size_t tlsf_block_size_max(void);
size_t tlsf_pool_overhead(void);
size_t tlsf_alloc_overhead(void);
/* Debugging. */
typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
/* Returns nonzero if any internal consistency check fails. */
int tlsf_check(tlsf_t tlsf);
int tlsf_check_pool(pool_t pool);
#if defined(__cplusplus)
};
#endif
#endif

23
lib/microReticulum/src/main.cpp Executable file
View File

@@ -0,0 +1,23 @@
// Only include if building locally and NOT testing
#if defined(LIBRARY_TEST) && !defined(PIO_UNIT_TESTING)
#include <stdio.h>
#ifdef ARDUINO
#include <Arduino.h>
void setup() {
Serial.begin(115200);
Serial.print("\nSilly rabbit, microReticulum is a library!\n\nSee the examples directory for example programs that make use of this library.\n\n");
}
void loop() {
}
#else
int main(void) {
printf("\nSilly rabbit, microReticulum is a library!\n\nSee the examples directory for example programs that make use of this library.\n\n");
}
#endif
#endif