From 5ed70dcca998f46476e219b1f303127546110242 Mon Sep 17 00:00:00 2001 From: James L Date: Mon, 23 Feb 2026 18:08:29 -0500 Subject: [PATCH] =?UTF-8?q?v1.0.0:=20Boundary=20mode=20with=20bidirectiona?= =?UTF-8?q?l=20LoRa=E2=86=94TCP=20transport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- BoundaryMode.h | 2 +- RNode_Firmware.ino | 1 + TcpInterface.h | 8 +- flash.py | 0 lib/microReticulum/LICENSE | 201 + lib/microReticulum/library.json | 31 + lib/microReticulum/library.properties | 10 + lib/microReticulum/src/Bytes.cpp | 201 + lib/microReticulum/src/Bytes.h | 447 ++ lib/microReticulum/src/Channel.cpp | 12 + lib/microReticulum/src/Channel.h | 57 + lib/microReticulum/src/Cryptography/AES.h | 89 + lib/microReticulum/src/Cryptography/CBC.cpp | 171 + lib/microReticulum/src/Cryptography/CBC.h | 66 + .../src/Cryptography/Ed25519.cpp | 3 + lib/microReticulum/src/Cryptography/Ed25519.h | 102 + .../src/Cryptography/Fernet.cpp | 116 + lib/microReticulum/src/Cryptography/Fernet.h | 41 + lib/microReticulum/src/Cryptography/HKDF.cpp | 28 + lib/microReticulum/src/Cryptography/HKDF.h | 9 + lib/microReticulum/src/Cryptography/HMAC.h | 109 + .../src/Cryptography/Hashes.cpp | 36 + lib/microReticulum/src/Cryptography/Hashes.h | 12 + lib/microReticulum/src/Cryptography/PKCS7.h | 66 + lib/microReticulum/src/Cryptography/Random.h | 38 + lib/microReticulum/src/Cryptography/Token.cpp | 159 + lib/microReticulum/src/Cryptography/Token.h | 47 + .../src/Cryptography/X25519.cpp | 3 + lib/microReticulum/src/Cryptography/X25519.h | 203 + lib/microReticulum/src/Destination.cpp | 486 ++ lib/microReticulum/src/Destination.h | 259 + lib/microReticulum/src/FileStream.h | 144 + lib/microReticulum/src/FileSystem.h | 129 + lib/microReticulum/src/Identity.cpp | 661 +++ lib/microReticulum/src/Identity.h | 200 + lib/microReticulum/src/Interface.cpp | 104 + lib/microReticulum/src/Interface.h | 268 + lib/microReticulum/src/Link.cpp | 1836 +++++++ lib/microReticulum/src/Link.h | 270 ++ lib/microReticulum/src/LinkData.h | 135 + lib/microReticulum/src/Log.cpp | 111 + lib/microReticulum/src/Log.h | 124 + lib/microReticulum/src/Packet.cpp | 995 ++++ lib/microReticulum/src/Packet.h | 382 ++ lib/microReticulum/src/Resource.cpp | 122 + lib/microReticulum/src/Resource.h | 110 + lib/microReticulum/src/ResourceData.h | 31 + lib/microReticulum/src/Reticulum.cpp | 492 ++ lib/microReticulum/src/Reticulum.h | 182 + lib/microReticulum/src/Transport.cpp | 4305 +++++++++++++++++ lib/microReticulum/src/Transport.h | 504 ++ lib/microReticulum/src/Type.h | 549 +++ lib/microReticulum/src/Utilities/Crc.cpp | 16 + lib/microReticulum/src/Utilities/Crc.h | 17 + lib/microReticulum/src/Utilities/OS.cpp | 289 ++ lib/microReticulum/src/Utilities/OS.h | 234 + .../src/Utilities/Persistence.cpp | 9 + .../src/Utilities/Persistence.h | 699 +++ lib/microReticulum/src/Utilities/Print.cpp | 354 ++ lib/microReticulum/src/Utilities/Print.h | 121 + lib/microReticulum/src/Utilities/Stream.cpp | 339 ++ lib/microReticulum/src/Utilities/Stream.h | 143 + lib/microReticulum/src/Utilities/tlsf.c | 1268 +++++ lib/microReticulum/src/Utilities/tlsf.h | 90 + lib/microReticulum/src/main.cpp | 23 + platformio.ini | 30 - 66 files changed, 18264 insertions(+), 35 deletions(-) mode change 100644 => 100755 BoundaryMode.h mode change 100644 => 100755 TcpInterface.h mode change 100644 => 100755 flash.py create mode 100755 lib/microReticulum/LICENSE create mode 100755 lib/microReticulum/library.json create mode 100644 lib/microReticulum/library.properties create mode 100755 lib/microReticulum/src/Bytes.cpp create mode 100755 lib/microReticulum/src/Bytes.h create mode 100755 lib/microReticulum/src/Channel.cpp create mode 100755 lib/microReticulum/src/Channel.h create mode 100755 lib/microReticulum/src/Cryptography/AES.h create mode 100755 lib/microReticulum/src/Cryptography/CBC.cpp create mode 100755 lib/microReticulum/src/Cryptography/CBC.h create mode 100755 lib/microReticulum/src/Cryptography/Ed25519.cpp create mode 100755 lib/microReticulum/src/Cryptography/Ed25519.h create mode 100755 lib/microReticulum/src/Cryptography/Fernet.cpp create mode 100755 lib/microReticulum/src/Cryptography/Fernet.h create mode 100755 lib/microReticulum/src/Cryptography/HKDF.cpp create mode 100755 lib/microReticulum/src/Cryptography/HKDF.h create mode 100755 lib/microReticulum/src/Cryptography/HMAC.h create mode 100755 lib/microReticulum/src/Cryptography/Hashes.cpp create mode 100755 lib/microReticulum/src/Cryptography/Hashes.h create mode 100755 lib/microReticulum/src/Cryptography/PKCS7.h create mode 100755 lib/microReticulum/src/Cryptography/Random.h create mode 100755 lib/microReticulum/src/Cryptography/Token.cpp create mode 100755 lib/microReticulum/src/Cryptography/Token.h create mode 100755 lib/microReticulum/src/Cryptography/X25519.cpp create mode 100755 lib/microReticulum/src/Cryptography/X25519.h create mode 100755 lib/microReticulum/src/Destination.cpp create mode 100755 lib/microReticulum/src/Destination.h create mode 100755 lib/microReticulum/src/FileStream.h create mode 100755 lib/microReticulum/src/FileSystem.h create mode 100755 lib/microReticulum/src/Identity.cpp create mode 100755 lib/microReticulum/src/Identity.h create mode 100755 lib/microReticulum/src/Interface.cpp create mode 100755 lib/microReticulum/src/Interface.h create mode 100755 lib/microReticulum/src/Link.cpp create mode 100755 lib/microReticulum/src/Link.h create mode 100755 lib/microReticulum/src/LinkData.h create mode 100755 lib/microReticulum/src/Log.cpp create mode 100755 lib/microReticulum/src/Log.h create mode 100755 lib/microReticulum/src/Packet.cpp create mode 100755 lib/microReticulum/src/Packet.h create mode 100755 lib/microReticulum/src/Resource.cpp create mode 100755 lib/microReticulum/src/Resource.h create mode 100755 lib/microReticulum/src/ResourceData.h create mode 100755 lib/microReticulum/src/Reticulum.cpp create mode 100755 lib/microReticulum/src/Reticulum.h create mode 100755 lib/microReticulum/src/Transport.cpp create mode 100755 lib/microReticulum/src/Transport.h create mode 100755 lib/microReticulum/src/Type.h create mode 100755 lib/microReticulum/src/Utilities/Crc.cpp create mode 100755 lib/microReticulum/src/Utilities/Crc.h create mode 100755 lib/microReticulum/src/Utilities/OS.cpp create mode 100755 lib/microReticulum/src/Utilities/OS.h create mode 100755 lib/microReticulum/src/Utilities/Persistence.cpp create mode 100755 lib/microReticulum/src/Utilities/Persistence.h create mode 100755 lib/microReticulum/src/Utilities/Print.cpp create mode 100755 lib/microReticulum/src/Utilities/Print.h create mode 100755 lib/microReticulum/src/Utilities/Stream.cpp create mode 100755 lib/microReticulum/src/Utilities/Stream.h create mode 100755 lib/microReticulum/src/Utilities/tlsf.c create mode 100755 lib/microReticulum/src/Utilities/tlsf.h create mode 100755 lib/microReticulum/src/main.cpp diff --git a/BoundaryMode.h b/BoundaryMode.h old mode 100644 new mode 100755 index d7c97c3..293af38 --- a/BoundaryMode.h +++ b/BoundaryMode.h @@ -20,7 +20,7 @@ // The boundary node operates with TWO RNS interfaces: // // 1. LoRaInterface (MODE_GATEWAY) — radio side, handles LoRa mesh -// 2. TcpInterface (MODE_BOUNDARY) — WiFi side, connects to TCP backbone +// 2. BackboneInterface (MODE_BOUNDARY) — WiFi side, connects to TCP backbone // // RNS Transport is ALWAYS enabled in boundary mode. // Packets received on either interface are routed through Transport diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index 878a52a..b931119 100755 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -680,6 +680,7 @@ void setup() { ); tcp_rns_interface = tcp_interface_ptr; tcp_rns_interface.mode(RNS::Type::Interface::MODE_BOUNDARY); + tcp_rns_interface.is_backbone(true); RNS::Transport::register_interface(tcp_rns_interface); { diff --git a/TcpInterface.h b/TcpInterface.h old mode 100644 new mode 100755 index f61de3b..30d88d5 --- a/TcpInterface.h +++ b/TcpInterface.h @@ -1,9 +1,9 @@ // Copyright (C) 2026, Boundary Mode Extension // Based on microReticulum_Firmware by Mark Qvist // -// TcpInterface — An RNS InterfaceImpl that bridges the WiFi TCP -// connection as a second RNS transport interface, enabling -// Boundary mode operation between LoRa and TCP/IP backbone. +// TcpInterface — An RNS InterfaceImpl that bridges a WiFi TCP +// connection as an RNS transport interface. Used for both the +// backbone (BackboneInterface) and local AP (LocalTcpInterface). // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -61,7 +61,7 @@ class TcpInterface : public RNS::InterfaceImpl { public: TcpInterface(TcpIfMode mode, uint16_t port = TCP_IF_DEFAULT_PORT, const char* target_host = nullptr, uint16_t target_port = 0, - const char* name = "TcpInterface") + const char* name = "BackboneInterface") : RNS::InterfaceImpl(name), _mode(mode), _port(port), diff --git a/flash.py b/flash.py old mode 100644 new mode 100755 diff --git a/lib/microReticulum/LICENSE b/lib/microReticulum/LICENSE new file mode 100755 index 0000000..261eeb9 --- /dev/null +++ b/lib/microReticulum/LICENSE @@ -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. diff --git a/lib/microReticulum/library.json b/lib/microReticulum/library.json new file mode 100755 index 0000000..b7fb833 --- /dev/null +++ b/lib/microReticulum/library.json @@ -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" + ] +} \ No newline at end of file diff --git a/lib/microReticulum/library.properties b/lib/microReticulum/library.properties new file mode 100644 index 0000000..ace0975 --- /dev/null +++ b/lib/microReticulum/library.properties @@ -0,0 +1,10 @@ +name=microReticulum +version=0.2.4 +author=Chad Attermann +maintainer=Chad Attermann +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 diff --git a/lib/microReticulum/src/Bytes.cpp b/lib/microReticulum/src/Bytes.cpp new file mode 100755 index 0000000..0d1e001 --- /dev/null +++ b/lib/microReticulum/src/Bytes.cpp @@ -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}; +} diff --git a/lib/microReticulum/src/Bytes.h b/lib/microReticulum/src/Bytes.h new file mode 100755 index 0000000..120e1e3 --- /dev/null +++ b/lib/microReticulum/src/Bytes.h @@ -0,0 +1,447 @@ +#pragma once + +#include "Log.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +// 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 Data; + using Data = std::vector; + //typedef std::shared_ptr SharedData; + using SharedData = std::shared_ptr; + + 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 + 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 (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 + inline const Bytes& operator = (const Data& data) { + assign(data); + return *this; + } + // Move assignment from std::vector + 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()); + } + inline bool canConvertFromJson(JsonVariantConst src, const RNS::Bytes&) { + return src.is(); + } +} diff --git a/lib/microReticulum/src/Channel.cpp b/lib/microReticulum/src/Channel.cpp new file mode 100755 index 0000000..ba6e66c --- /dev/null +++ b/lib/microReticulum/src/Channel.cpp @@ -0,0 +1,12 @@ +#include "Channel.h" + +#include "Reticulum.h" +#include "Transport.h" +#include "Packet.h" +#include "Log.h" + +#include + +using namespace RNS; +using namespace RNS::Type::Channel; +using namespace RNS::Utilities; diff --git a/lib/microReticulum/src/Channel.h b/lib/microReticulum/src/Channel.h new file mode 100755 index 0000000..4425be7 --- /dev/null +++ b/lib/microReticulum/src/Channel.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Destination.h" +#include "Type.h" + +#include +#include + +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; + + }; + + + class ChannelAdvertisement { + + }; + +} diff --git a/lib/microReticulum/src/Cryptography/AES.h b/lib/microReticulum/src/Cryptography/AES.h new file mode 100755 index 0000000..d5927e5 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/AES.h @@ -0,0 +1,89 @@ +#pragma once + +#include "CBC.h" + +#include "../Bytes.h" + +#include + +namespace RNS { namespace Cryptography { + + class AES_128_CBC { + + public: + static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) { + CBC 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 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 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 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 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 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 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 cbc; + cbc.setKey(key.data(), key.size()); + cbc.setIV(iv.data(), iv.size()); + cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size()); + } + + }; + +} } diff --git a/lib/microReticulum/src/Cryptography/CBC.cpp b/lib/microReticulum/src/Cryptography/CBC.cpp new file mode 100755 index 0000000..b18e5ce --- /dev/null +++ b/lib/microReticulum/src/Cryptography/CBC.cpp @@ -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 +#include + +/** + * \class CBCCommon 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 + * \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 cbc; + * cbc.setKey(key, 24); + * cbc.setIV(iv, 16); + * cbc.encrypt(output, input, len); + * \endcode + * + * Decryption is similar: + * + * \code + * CBC 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. + */ \ No newline at end of file diff --git a/lib/microReticulum/src/Cryptography/CBC.h b/lib/microReticulum/src/Cryptography/CBC.h new file mode 100755 index 0000000..8bb476d --- /dev/null +++ b/lib/microReticulum/src/Cryptography/CBC.h @@ -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 +#include + +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 +class CBC : public CBCCommon +{ +public: + CBC() { setBlockCipher(&cipher); } + +private: + T cipher; +}; + +#endif \ No newline at end of file diff --git a/lib/microReticulum/src/Cryptography/Ed25519.cpp b/lib/microReticulum/src/Cryptography/Ed25519.cpp new file mode 100755 index 0000000..a69c397 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Ed25519.cpp @@ -0,0 +1,3 @@ +#include "Ed25519.h" + +using namespace RNS::Cryptography; diff --git a/lib/microReticulum/src/Cryptography/Ed25519.h b/lib/microReticulum/src/Cryptography/Ed25519.h new file mode 100755 index 0000000..69cb396 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Ed25519.h @@ -0,0 +1,102 @@ +#pragma once + +#include "Bytes.h" + +#include + +#include + +/* + +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; + + 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; + + 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; + + }; + +} } diff --git a/lib/microReticulum/src/Cryptography/Fernet.cpp b/lib/microReticulum/src/Cryptography/Fernet.cpp new file mode 100755 index 0000000..f70bc6e --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Fernet.cpp @@ -0,0 +1,116 @@ +#include "Fernet.h" + +#include "HMAC.h" +#include "PKCS7.h" +#include "AES.h" +#include "../Log.h" + +#include +#include + +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"); + } +} \ No newline at end of file diff --git a/lib/microReticulum/src/Cryptography/Fernet.h b/lib/microReticulum/src/Cryptography/Fernet.h new file mode 100755 index 0000000..914e98a --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Fernet.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Random.h" +#include "../Bytes.h" + +#include + +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; + + 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; + }; + +} } diff --git a/lib/microReticulum/src/Cryptography/HKDF.cpp b/lib/microReticulum/src/Cryptography/HKDF.cpp new file mode 100755 index 0000000..966756f --- /dev/null +++ b/lib/microReticulum/src/Cryptography/HKDF.cpp @@ -0,0 +1,28 @@ +#include "HKDF.h" + +#include +#include + +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 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; +} diff --git a/lib/microReticulum/src/Cryptography/HKDF.h b/lib/microReticulum/src/Cryptography/HKDF.h new file mode 100755 index 0000000..903c31b --- /dev/null +++ b/lib/microReticulum/src/Cryptography/HKDF.h @@ -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}); + +} } diff --git a/lib/microReticulum/src/Cryptography/HMAC.h b/lib/microReticulum/src/Cryptography/HMAC.h new file mode 100755 index 0000000..92a5bce --- /dev/null +++ b/lib/microReticulum/src/Cryptography/HMAC.h @@ -0,0 +1,109 @@ +#pragma once + +#include "../Bytes.h" + +#include +#include +#include +#include +#include +#include + +namespace RNS { namespace Cryptography { + + class HMAC { + + public: + enum Digest { + DIGEST_NONE, + DIGEST_SHA256, + DIGEST_SHA512, + }; + + using Ptr = std::shared_ptr; + + 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(new SHA256()); + break; + case DIGEST_SHA512: + _hash = std::unique_ptr(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; + + }; + + /* + 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(); + } + +} } diff --git a/lib/microReticulum/src/Cryptography/Hashes.cpp b/lib/microReticulum/src/Cryptography/Hashes.cpp new file mode 100755 index 0000000..5687efa --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Hashes.cpp @@ -0,0 +1,36 @@ +#include "Hashes.h" + +#include "../Bytes.h" + +#include +#include + +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; +} diff --git a/lib/microReticulum/src/Cryptography/Hashes.h b/lib/microReticulum/src/Cryptography/Hashes.h new file mode 100755 index 0000000..c08df49 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Hashes.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../Bytes.h" + +#include + +namespace RNS { namespace Cryptography { + + const Bytes sha256(const Bytes& data); + const Bytes sha512(const Bytes& data); + +} } diff --git a/lib/microReticulum/src/Cryptography/PKCS7.h b/lib/microReticulum/src/Cryptography/PKCS7.h new file mode 100755 index 0000000..4167d31 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/PKCS7.h @@ -0,0 +1,66 @@ +#pragma once + +#include "../Bytes.h" +//#include "../Log.h" + +#include + +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())); + } + + }; + +} } diff --git a/lib/microReticulum/src/Cryptography/Random.h b/lib/microReticulum/src/Cryptography/Random.h new file mode 100755 index 0000000..3534c9c --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Random.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Bytes.h" + +#include +#include + +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); + } + +} } diff --git a/lib/microReticulum/src/Cryptography/Token.cpp b/lib/microReticulum/src/Cryptography/Token.cpp new file mode 100755 index 0000000..e91c7e8 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Token.cpp @@ -0,0 +1,159 @@ +#include "Token.h" + +#include "HMAC.h" +#include "PKCS7.h" +#include "AES.h" +#include "../Log.h" + +#include +#include + +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"); + } +} \ No newline at end of file diff --git a/lib/microReticulum/src/Cryptography/Token.h b/lib/microReticulum/src/Cryptography/Token.h new file mode 100755 index 0000000..3d3f343 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/Token.h @@ -0,0 +1,47 @@ +#pragma once + +#include "Random.h" +#include "../Bytes.h" +#include "../Type.h" + +#include + +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; + + 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; + }; + +} } diff --git a/lib/microReticulum/src/Cryptography/X25519.cpp b/lib/microReticulum/src/Cryptography/X25519.cpp new file mode 100755 index 0000000..5e7cc31 --- /dev/null +++ b/lib/microReticulum/src/Cryptography/X25519.cpp @@ -0,0 +1,3 @@ +#include "X25519.h" + +using namespace RNS::Cryptography; diff --git a/lib/microReticulum/src/Cryptography/X25519.h b/lib/microReticulum/src/Cryptography/X25519.h new file mode 100755 index 0000000..75c009b --- /dev/null +++ b/lib/microReticulum/src/Cryptography/X25519.h @@ -0,0 +1,203 @@ +#pragma once + +#include "Bytes.h" +#include "Log.h" + +#include + +#include +#include +#include + +namespace RNS { namespace Cryptography { + + class X25519PublicKey { + + public: + using Ptr = std::shared_ptr; + + 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; + + 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; + + }; + +} } diff --git a/lib/microReticulum/src/Destination.cpp b/lib/microReticulum/src/Destination.cpp new file mode 100755 index 0000000..d15a460 --- /dev/null +++ b/lib/microReticulum/src/Destination.cpp @@ -0,0 +1,486 @@ +#include "Destination.h" + +#include "Transport.h" +#include "Interface.h" +#include "Packet.h" +#include "Log.h" + +#include +#include +#include + +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 Destination::app_and_aspects_from_name(const char* full_name) { + //p components = full_name.split(".") + //p return (components[0], components[1:]) + std::vector 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 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`. 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` 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); +} diff --git a/lib/microReticulum/src/Destination.h b/lib/microReticulum/src/Destination.h new file mode 100755 index 0000000..f741c9c --- /dev/null +++ b/lib/microReticulum/src/Destination.h @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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 _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`. 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; + + 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 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& path_responses() const { assert(_object); return _object->_path_responses; } + inline const std::map& 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 _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 _path_responses; + std::set _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; + + }; + +} \ No newline at end of file diff --git a/lib/microReticulum/src/FileStream.h b/lib/microReticulum/src/FileStream.h new file mode 100755 index 0000000..6b69d65 --- /dev/null +++ b/lib/microReticulum/src/FileStream.h @@ -0,0 +1,144 @@ +#pragma once + +#include "Utilities/Crc.h" +#include "Log.h" +#include "Bytes.h" +#include "Type.h" + +#ifdef ARDUINO +#include +#else +#include "Utilities/Stream.h" +#endif + +#include +#include +#include +#include + +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 _impl; + uint32_t _crc = 0; + }; + +} diff --git a/lib/microReticulum/src/FileSystem.h b/lib/microReticulum/src/FileSystem.h new file mode 100755 index 0000000..f224420 --- /dev/null +++ b/lib/microReticulum/src/FileSystem.h @@ -0,0 +1,129 @@ +#pragma once + +#include "FileStream.h" +#include "Log.h" +#include "Bytes.h" +#include "Type.h" + +#include +#include +#include +#include + +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 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 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 _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 _impl; + }; + +} diff --git a/lib/microReticulum/src/Identity.cpp b/lib/microReticulum/src/Identity.cpp new file mode 100755 index 0000000..0619683 --- /dev/null +++ b/lib/microReticulum/src/Identity.cpp @@ -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 +#include + +using namespace RNS; +using namespace RNS::Type::Identity; +using namespace RNS::Cryptography; +using namespace RNS::Utilities; + +/*static*/ std::map 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` 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` data +:returns: A :ref:`RNS.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` instance that can be used to create an outgoing :ref:`RNS.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 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> sorted_pairs; + // Copy key/value pairs from map into vector + std::for_each(_known_destinations.begin(), _known_destinations.end(), [&](const std::pair& ref) { + sorted_pairs.push_back(ref); + }); + // Sort vector using specified comparator + std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair &left, const std::pair &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}); +} diff --git a/lib/microReticulum/src/Identity.h b/lib/microReticulum/src/Identity.h new file mode 100755 index 0000000..0459aa1 --- /dev/null +++ b/lib/microReticulum/src/Identity.h @@ -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 +#include +#include +#include + +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 _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; + + }; + +} \ No newline at end of file diff --git a/lib/microReticulum/src/Interface.cpp b/lib/microReticulum/src/Interface.cpp new file mode 100755 index 0000000..18bde30 --- /dev/null +++ b/lib/microReticulum/src/Interface.cpp @@ -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 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()); + 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()); + } +} +*/ diff --git a/lib/microReticulum/src/Interface.h b/lib/microReticulum/src/Interface.h new file mode 100755 index 0000000..166b4be --- /dev/null +++ b/lib/microReticulum/src/Interface.h @@ -0,0 +1,268 @@ +#pragma once + +#include "Identity.h" +#include "Log.h" +#include "Bytes.h" +#include "Type.h" + +#include + +#include +#include +#include +#include + +namespace RNS { + + class Interface; + using HInterface = std::shared_ptr; + + 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 { + + 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 _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& 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& 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 _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() && strlen(src.as()) == 64; + } +} +*/ +/* +namespace ArduinoJson { + template <> + struct Converter { + 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()); + 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() && strlen(src.as()) == 64; + } + }; +} +*/ \ No newline at end of file diff --git a/lib/microReticulum/src/Link.cpp b/lib/microReticulum/src/Link.cpp new file mode 100755 index 0000000..d358f8e --- /dev/null +++ b/lib/microReticulum/src/Link.cpp @@ -0,0 +1,1836 @@ +#include "Link.h" + +#include "LinkData.h" +#include "Resource.h" +#include "Reticulum.h" +#include "Transport.h" +#include "Packet.h" +#include "Log.h" +#include "Cryptography/Ed25519.h" +#include "Cryptography/X25519.h" +#include "Cryptography/HKDF.h" +#include "Cryptography/Token.h" +#include "Cryptography/Random.h" +#include "Utilities/OS.h" + +#define MSGPACK_DEBUGLOG_ENABLE 0 +#include + +#include + +#include + +using namespace RNS; +using namespace RNS::Type::Link; +using namespace RNS::Cryptography; +using namespace RNS::Utilities; + +/*static*/ uint8_t Link::resource_strategies = ACCEPT_NONE | ACCEPT_APP | ACCEPT_ALL; + +/*static*/ std::set Link::ENABLED_MODES = {MODE_AES256_CBC}; +/*static*/ link_mode Link::MODE_DEFAULT = MODE_AES256_CBC; + +Link::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}*/, link_mode mode /*= MODE_DEFAULT*/) : + _object(new LinkData(destination)) +{ + assert(_object); + + _object->_owner = owner; + _object->_mode = mode; + + if (destination && destination.type() != Type::Destination::SINGLE) { + throw std::logic_error("Links can only be established to the \"single\" destination type"); + } + + if (!destination) { + _object->_initiator = false; + _object->_prv = Cryptography::X25519PrivateKey::generate(); + // CBA BUG: not checking for owner + if (_object->_owner) { + _object->_sig_prv = _object->_owner.identity().sig_prv(); + } + } + else { + _object->_initiator = true; + _object->_expected_hops = Transport::hops_to(_object->_destination.hash()); + _object->_establishment_timeout = Reticulum::get_instance().get_first_hop_timeout(destination.hash()); + _object->_prv = Cryptography::X25519PrivateKey::generate(); + _object->_sig_prv = Cryptography::Ed25519PrivateKey::generate(); + } + + _object->_pub = _object->_prv->public_key(); + _object->_pub_bytes = _object->_pub->public_bytes(); + TRACEF("Link::load_private_key: pub bytes: %s", _object->_pub_bytes.toHex().c_str()); + + _object->_sig_pub = _object->_sig_prv->public_key(); + _object->_sig_pub_bytes = _object->_sig_pub->public_bytes(); + TRACEF("Link::load_private_key: sig pub bytes: %s", _object->_sig_pub_bytes.toHex().c_str()); + + if (!peer_pub_bytes) { + _object->_peer_pub = nullptr; + _object->_peer_pub_bytes = nullptr; + } + else { + load_peer(peer_pub_bytes, peer_sig_pub_bytes); + } + + if (established_callback) { + set_link_established_callback(established_callback); + } + + if (closed_callback) { + set_link_closed_callback(closed_callback); + } + + if (_object->_initiator) { + Bytes signalling_bytes; + uint16_t nh_hw_mtu = Transport::next_hop_interface_hw_mtu(destination.hash()); + if (RNS::Reticulum::link_mtu_discovery() && nh_hw_mtu) { + signalling_bytes = Link::signalling_bytes(nh_hw_mtu, _object->_mode); + DEBUGF("Signalling link MTU of %d for link", nh_hw_mtu); + } + else { + signalling_bytes = Link::signalling_bytes(RNS::Type::Reticulum::MTU, _object->_mode); + } + TRACEF("Establishing link with mode %d", _object->_mode); + //p self.request_data = self.pub_bytes+self.sig_pub_bytes+signalling_bytes + _object->_request_data = _object->_pub_bytes + _object->_sig_pub_bytes + signalling_bytes; + _object->_packet = Packet(destination, _object->_request_data, RNS::Type::Packet::LINKREQUEST); + _object->_packet.pack(); + _object->_establishment_cost += _object->_packet.raw().size(); + set_link_id(_object->_packet); + Transport::register_link(*this); + _object->_request_time = OS::time(); + start_watchdog(); + _object->_packet.send(); + had_outbound(); + DEBUGF("Link request %s sent to %s", _object->_link_id.toHex().c_str(), _object->_destination.toString().c_str()); + TRACEF("Establishment timeout is %f for link request %s", _object->_establishment_timeout, _object->_link_id.toHex().c_str()); + } + + // CBA LINK + //_object->_link_destination = Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::LINK, hash()); + //_object->_link_destination.link_id(_object->_link_id); + + MEM("Link object created"); +} + + +/*static*/ Bytes Link::signalling_bytes(uint16_t mtu, link_mode mode) { + //p if not mode in Link.ENABLED_MODES: raise TypeError(f"Requested link mode {Link.MODE_DESCRIPTIONS[mode]} not enabled") + //if (!(mode & ENABLED_MODES)) throw std::runtime_error("Requested link mode "+std::to_string(mode)+" not enabled"); + if (Link::ENABLED_MODES.find(mode) == Link::ENABLED_MODES.end()) throw std::runtime_error("Requested link mode "+std::to_string(mode)+" not enabled"); + //p signalling_value = (mtu & Link.MTU_BYTEMASK)+(((mode<<5) & Link.MODE_BYTEMASK)<<16) + //p return struct.pack(">I", signalling_value)[1:] + uint32_t signalling_value = OS::portable_htonl((mtu & MTU_BYTEMASK)+(((mode<<5) & MODE_BYTEMASK)<<16)); + Bytes data(((uint8_t*)&signalling_value)+1, 3); + return data; +} + +/*static*/ uint16_t Link::mtu_from_lr_packet(const Packet& packet) { + if (packet.data().size() == ECPUBSIZE+LINK_MTU_SIZE) { + return (packet.data()[ECPUBSIZE] << 16) + (packet.data()[ECPUBSIZE+1] << 8) + (packet.data()[ECPUBSIZE+2]) & MTU_BYTEMASK; + } + return 0; +} + +/*static*/ uint16_t Link::mtu_from_lp_packet(const Packet& packet) { + //p if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE: + if (packet.data().size() == RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2+LINK_MTU_SIZE) { + Bytes mtu_bytes = packet.data().mid(RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2, LINK_MTU_SIZE); + return (mtu_bytes[0] << 16) + (mtu_bytes[1] << 8) + (mtu_bytes[2]) & MTU_BYTEMASK; + } + return 0; +} + +/*static*/ uint8_t Link::mode_byte(link_mode mode) { + //p if mode in Link.ENABLED_MODES: return (mode << 5) & Link.MODE_BYTEMASK + if (Link::ENABLED_MODES.find(mode) != Link::ENABLED_MODES.end()) return (mode << 5) & MODE_BYTEMASK; + throw std::runtime_error("Requested link mode {mode} not enabled"); +} + +/*static*/ link_mode Link::mode_from_lr_packet(const Packet& packet) { + if (packet.data().size() > ECPUBSIZE) { + link_mode mode = static_cast((packet.data()[ECPUBSIZE] & MODE_BYTEMASK) >> 5); + return mode; + } + return Link::MODE_DEFAULT; +} + +/*static*/ link_mode Link::mode_from_lp_packet(const Packet& packet) { + if (packet.data().size() > RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2) { + link_mode mode = static_cast(packet.data()[RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2] >> 5); + return mode; + } + return Link::MODE_DEFAULT; +} + +/*static*/ Bytes Link::link_id_from_lr_packet(const Packet& packet) { + Bytes hashable_part = packet.get_hashable_part(); + if (packet.data().size() > ECPUBSIZE) { + size_t diff = packet.data().size() - ECPUBSIZE; + //p hashable_part = hashable_part[:-diff] + hashable_part = hashable_part.left(hashable_part.size() - diff); + } + return RNS::Identity::truncated_hash(hashable_part); +} + +/*static*/ Link Link::validate_request( const Destination& owner, const Bytes& data, const Packet& packet) { + if (data.size() == ECPUBSIZE || data.size() == ECPUBSIZE + LINK_MTU_SIZE) { + try { + Link link({Type::NONE}, nullptr, nullptr, owner, data.left(ECPUBSIZE/2), data.mid(ECPUBSIZE/2, ECPUBSIZE/2)); + link.set_link_id(packet); + + if (data.size() == ECPUBSIZE + LINK_MTU_SIZE) { + DEBUG("Link request includes MTU signalling"); // TODO: Remove debug + try { + uint16_t mtu = mtu_from_lr_packet(packet); + link.mtu((mtu != 0) ? mtu : Type::Reticulum::MTU); + } + catch (std::exception& e) { + ERRORF("An error ocurred while validating link request %s", link.link_id().toHex().c_str()); + link.mtu(Type::Reticulum::MTU); + } + } + + link.mode(mode_from_lr_packet(packet)); + + // TODO: Remove debug + DEBUGF("Incoming link request with mode %d", link.get_mode()); + + link.update_mdu(); + link.destination(packet.destination()); + link.establishment_timeout(ESTABLISHMENT_TIMEOUT_PER_HOP * std::max((uint8_t)1, packet.hops()) + KEEPALIVE); + link.establishment_cost(link.establishment_cost() + packet.raw().size()); + VERBOSEF("Validating link request %s", link.link_id().toHex().c_str()); + TRACEF("Link MTU configured to %d", link.mtu()); + TRACEF("Establishment timeout is %f for incoming link request %s", link.establishment_timeout(), link.link_id().toHex().c_str()); + link.handshake(); + link.attached_interface(packet.receiving_interface()); + link.prove(); + link.request_time(OS::time()); + Transport::register_link(link); + link.last_inbound(OS::time()); + link.start_watchdog(); + + DEBUGF("Incoming link request %s accepted", link.toString().c_str()); + return link; + } + catch (std::exception& e) { + VERBOSEF("Validating link request failed, exception: %s", e.what()); + return {Type::NONE}; + } + } + else { + DEBUGF("Invalid link request payload size (%zu), dropping request", data.size()); + return {Type::NONE}; + } +} + +void Link::load_peer(const Bytes& peer_pub_bytes, const Bytes& peer_sig_pub_bytes) { + assert(_object); + _object->_peer_pub_bytes = peer_pub_bytes; + _object->_peer_pub = Cryptography::X25519PublicKey::from_public_bytes(_object->_peer_pub_bytes); + + _object->_peer_sig_pub_bytes = peer_sig_pub_bytes; + _object->_peer_sig_pub = Cryptography::Ed25519PublicKey::from_public_bytes(_object->_peer_sig_pub_bytes); + + // CBA TODO Determine the purpose of the following. + // X25519PublicKey does not have a member "curve" and this is not accessed anywhere +/* + if not hasattr(self.peer_pub, "curve") { + self.peer_pub.curve = CURVE; + } +*/ +} + +void Link::set_link_id(const Packet& packet) { + assert(_object); + _object->_link_id = Link::link_id_from_lr_packet(packet); + _object->_hash = _object->_link_id; +} + +void Link::handshake() { + assert(_object); + if (_object->_status == Type::Link::PENDING && _object->_prv) { + _object->_status = Type::Link::HANDSHAKE; + _object->_shared_key = _object->_prv->exchange(_object->_peer_pub_bytes); + + uint16_t derived_key_length; + if (_object->_mode == RNS::Type::Link::MODE_AES128_CBC) derived_key_length = 32; + else if (_object->_mode == RNS::Type::Link::MODE_AES256_CBC) derived_key_length = 64; + else throw new std::invalid_argument("Invalid link mode "+std::to_string(_object->_mode)+" on "+toString()); + + _object->_derived_key = Cryptography::hkdf( + derived_key_length, + _object->_shared_key, + get_salt(), + get_context() + ); + } + else { + ERRORF("Handshake attempt on %s with invalid state %d", toString().c_str(), _object->_status); + } +} + +void Link::prove() { + assert(_object); + DEBUGF("Link %s requesting proof", link_id().toHex().c_str()); + Bytes signed_data =_object->_link_id + _object->_pub_bytes + _object->_sig_pub_bytes; + const Bytes signature(_object->_owner.identity().sign(signed_data)); + + Bytes proof_data = signature + _object->_pub_bytes; + // CBA LINK + // CBA TODO: Determine which approach is better, passing liunk to packet or passing _link_destination + Packet proof(*this, proof_data, Type::Packet::PROOF, Type::Packet::LRPROOF); + proof.send(); + _object->_establishment_cost += proof.raw().size(); + had_outbound(); +} + +void Link::prove_packet(const Packet& packet) { + assert(_object); + DEBUGF("Link %s proving packet", link_id().toHex().c_str()); + const Bytes signature(sign(packet.packet_hash())); + // TODO: Hardcoded as explicit proof for now + // if RNS.Reticulum.should_use_implicit_proof(): + // proof_data = signature + // else: + // proof_data = packet.packet_hash + signature + Bytes proof_data = packet.packet_hash() + signature; + + Packet proof(*this, proof_data, Type::Packet::PROOF); + proof.send(); + had_outbound(); +} + +void Link::validate_proof(const Packet& packet) { + assert(_object); + DEBUGF("Link %s validating proof", link_id().toHex().c_str()); + try { + if (_object->_status == Type::Link::PENDING) { + Bytes packet_data(packet.data()); + TRACEF("Link %s: initiator: %d", toString().c_str(), _object->_initiator); + TRACEF("Link %s: size: %d", toString().c_str(), packet_data.size()); + Bytes signalling_bytes; + uint16_t confirmed_mtu = 0; + link_mode mode = mode_from_lp_packet(packet); + DEBUGF("Validating link request proof with mode %d", mode); + if (mode != _object->_mode) throw std::runtime_error("Invalid link mode "+std::to_string(mode)+" in link request proof"); + //p if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE: + if (packet_data.size() == Type::Identity::SIGLENGTH/8+ECPUBSIZE/2+RNS::Type::Link::LINK_MTU_SIZE) { + confirmed_mtu = Link::mtu_from_lp_packet(packet); + signalling_bytes = Link::signalling_bytes(confirmed_mtu, mode); + // CBA TODO Determine best way to deal with packet.data() being read-only + //p packet.data = packet.data[:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2] + packet_data = packet_data.left(RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2); + DEBUGF("Destination confirmed link MTU of %d", confirmed_mtu); + } + //p if _object->_initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8+ECPUBSIZE//2: + if (_object->_initiator && packet_data.size() == Type::Identity::SIGLENGTH/8+ECPUBSIZE/2) { + //p peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+ECPUBSIZE//2] + const Bytes peer_pub_bytes(packet_data.mid(Type::Identity::SIGLENGTH/8, ECPUBSIZE/2)); + //p peer_sig_pub_bytes = _object->_destination.identity.get_public_key()[ECPUBSIZE//2:ECPUBSIZE] + const Bytes peer_sig_pub_bytes(_object->_destination.identity().get_public_key().mid(ECPUBSIZE/2, ECPUBSIZE/2)); + TRACEF("Link %s performing handshake", link_id().toHex().c_str()); + load_peer(peer_pub_bytes, peer_sig_pub_bytes); + handshake(); + + _object->_establishment_cost += packet.raw().size(); + Bytes signed_data = _object->_link_id + _object->_peer_pub_bytes + _object->_peer_sig_pub_bytes; + const Bytes signature(packet_data.left(Type::Identity::SIGLENGTH/8)); + + TRACEF("Link %s validating identity", link_id().toHex().c_str()); + if (_object->_destination.identity().validate(signature, signed_data)) { + if (_object->_status != Type::Link::HANDSHAKE) { + throw std::runtime_error("Invalid link state for proof validation: " + _object->_status); + } + _object->_rtt = OS::time() - _object->_request_time; + _object->_attached_interface = packet.receiving_interface(); + _object->__remote_identity = _object->_destination.identity(); + if (confirmed_mtu) _object->_mtu = confirmed_mtu; + else _object->_mtu = RNS::Type::Reticulum::MTU; + update_mdu(); + _object->_status = Type::Link::ACTIVE; + _object->_activated_at = OS::time(); + _object->_last_proof = _object->_activated_at; + Transport::activate_link(*this); + VERBOSEF("Link %s established with %s, RTT is %f s", toString().c_str(), _object->_destination.toString().c_str(), OS::round(_object->_rtt, 3)); + + //p if _object->_rtt != None and _object->_establishment_cost != None and _object->_rtt > 0 and _object->_establishment_cost > 0: + if (_object->_rtt != 0.0 && _object->_establishment_cost != 0 && _object->_rtt > 0 and _object->_establishment_cost > 0) { + _object->_establishment_rate = _object->_establishment_cost / _object->_rtt; + } + + //p rtt_data = umsgpack.packb(self.rtt) + MsgPack::Packer packer; + packer.serialize(_object->_rtt); + Bytes rtt_data(packer.data(), packer.size()); +TRACEF("***** RTT data size: %d", rtt_data.size()); + //p rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT) + Packet rtt_packet(*this, rtt_data, Type::Packet::DATA, Type::Packet::LRRTT); +TRACEF("***** RTT packet data: %s", rtt_packet.data().toHex().c_str()); +rtt_packet.pack(); +Packet test_packet(RNS::Destination(RNS::Type::NONE), rtt_packet.raw()); +test_packet.unpack(); +TRACEF("***** RTT test packet destination hash: %s", test_packet.destination_hash().toHex().c_str()); +TRACEF("***** RTT test packet data size: %d", test_packet.data().size()); +TRACEF("***** RTT test packet data: %s", test_packet.data().toHex().c_str()); +Bytes plaintext = decrypt(test_packet.data()); +TRACEF("***** RTT test packet plaintext: %s", plaintext.toHex().c_str()); + rtt_packet.send(); + had_outbound(); + + if (_object->_callbacks._established != nullptr) { + VERBOSEF("Link %s is established", link_id().toHex().c_str()); + //p thread = threading.Thread(target=_object->_callbacks.link_established, args=(self,)) + //p thread.daemon = True + //p thread.start() + _object->_callbacks._established(*this); + } + } + else { + DEBUGF("Invalid link proof signature received by %s. Ignoring.", toString().c_str()); + } + } + else { + DEBUGF("Failed initiator/size check for link proof signature received by %s. Ignoring.", toString().c_str()); + } + } + } + catch (std::exception& e) { + _object->_status = Type::Link::CLOSED; + ERRORF("An error ocurred while validating link request proof on %s.", toString().c_str()); + ERRORF("The contained exception was: %s", e.what()); + } +} + + +/* +Identifies the initiator of the link to the remote peer. This can only happen +once the link has been established, and is carried out over the encrypted link. +The identity is only revealed to the remote peer, and initiator anonymity is +thus preserved. This method can be used for authentication. + +:param identity: An RNS.Identity instance to identify as. +*/ +void Link::identify(const Identity& identity) { + assert(_object); + DEBUGF("Link %s requesting identity", link_id().toHex().c_str()); + if (_object->_initiator && _object->_status == Type::Link::ACTIVE) { + const Bytes signed_data(_object->_link_id + identity.get_public_key()); + const Bytes signature(identity.sign(signed_data)); + const Bytes proof_data(identity.get_public_key() + signature); + + Packet proof(*this, proof_data, Type::Packet::DATA, Type::Packet::LINKIDENTIFY); + proof.send(); + had_outbound(); + } +} + +/* +Sends a request to the remote peer. + +:param path: The request path. +:param response_callback: An optional function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example` for more info. +:param failed_callback: An optional function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example` for more info. +:param progress_callback: An optional function or method with the signature *progress_callback(request_receipt)* to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the *request_receipt.progress* property. +:param timeout: An optional timeout in seconds for the request. If *None* is supplied it will be calculated based on link RTT. +:returns: A :ref:`RNS.RequestReceipt` instance if the request was sent, or *False* if it was not. +*/ +const RNS::RequestReceipt Link::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*/) { + assert(_object); + DEBUGF("Link %s sending request", link_id().toHex().c_str()); + const Bytes request_path_hash(Identity::truncated_hash(path)); + + //p unpacked_request = [OS::time(), request_path_hash, data] + //p packed_request = umsgpack.packb(unpacked_request) + MsgPack::Packer packer; + packer.to_array(OS::time(), request_path_hash, data); + Bytes packed_request(packer.data(), packer.size()); + + if (timeout == 0.0) { + timeout = _object->_rtt * _object->_traffic_timeout_factor + Type::Resource::RESPONSE_MAX_GRACE_TIME * 1.125; + } + + if (packed_request.size() <= MDU) { + Packet request_packet(*this, packed_request, Type::Packet::DATA, Type::Packet::REQUEST); + PacketReceipt packet_receipt = request_packet.send(); + + if (!packet_receipt) { + return {Type::NONE}; + } + else { + packet_receipt.set_timeout(timeout); + return RequestReceipt( + *this, + packet_receipt, + {Type::NONE}, + response_callback, + failed_callback, + progress_callback, + timeout, + packed_request.size() + ); + } + } + else { + const Bytes request_id(Identity::truncated_hash(packed_request)); + DEBUGF("Sending request %s as resource.", request_id.toHex().c_str()); + Resource request_resource(packed_request, *this, request_id, false, timeout); + + return RequestReceipt( + *this, + {Type::NONE}, + request_resource, + response_callback, + failed_callback, + progress_callback, + timeout, + packed_request.size() + ); + } +} + + +void Link::update_mdu() { + assert(_object); + _object->_mdu = _object->_mtu - RNS::Type::Reticulum::HEADER_MAXSIZE - RNS::Type::Reticulum::IFAC_MIN_SIZE; + //p self.mdu = math.floor((self.mtu-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.TOKEN_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1 + _object->_mdu = floor((_object->_mtu-RNS::Type::Reticulum::IFAC_MIN_SIZE-RNS::Type::Reticulum::HEADER_MINSIZE-RNS::Type::Identity::TOKEN_OVERHEAD)/RNS::Type::Identity::AES128_BLOCKSIZE)*RNS::Type::Identity::AES128_BLOCKSIZE - 1; +} + +void Link::rtt_packet(const Packet& packet) { + assert(_object); + try { + double measured_rtt = OS::time() - _object->_request_time; + const Bytes plaintext(decrypt(packet.data())); + if (plaintext) { + //p rtt = umsgpack.unpackb(plaintext) + MsgPack::Unpacker unpacker; + unpacker.feed(plaintext.data(), plaintext.size()); + double rtt = 0.0; + unpacker.deserialize(rtt); + _object->_rtt = std::max(measured_rtt, rtt); + _object->_status = Type::Link::ACTIVE; + _object->_activated_at = OS::time(); + + //p if _object->_rtt != None and _object->_establishment_cost != None and _object->_rtt > 0 and _object->_establishment_cost > 0: + if (_object->_rtt != 0.0 && _object->_establishment_cost != 0.0 && _object->_rtt > 0 and _object->_establishment_cost > 0) { + _object->_establishment_rate = _object->_establishment_cost / _object->_rtt; + } + + try { + if (_object->_owner.callbacks()._link_established != nullptr) { + _object->_owner.callbacks()._link_established(*this); + } + } + catch (std::exception& e) { + ERRORF("Error occurred in external link establishment callback. The contained exception was: %s", e.what()); + } + } + } + catch (std::exception& e) { + ERRORF("Error occurred while processing RTT packet, tearing down link. The contained exception was: %s", e.what()); + teardown(); + } +} + +/* +:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second. +*/ +float Link::get_establishment_rate() { + assert(_object); + //p if _object->_establishment_rate != None: + //p return _object->_establishment_rate*8 + //p else: + //p return None + return _object->_establishment_rate*8; +} + +/* +:returns: The MTU of an established link. +*/ +uint16_t Link::get_mtu() { + assert(_object); + if (_object->_status == ACTIVE) { + return _object->_mtu; + } + return 0; +} + +/* +:returns: The packet MDU of an established link. +*/ +uint16_t Link::get_mdu() { + assert(_object); + if (_object->_status == ACTIVE) { + return _object->_mdu; + } + return 0; +} + +/* +:returns: The packet expected in-flight data rate of an established link. +*/ +float Link::get_expected_rate() { + assert(_object); + if (_object->_status == ACTIVE) { + return _object->_expected_rate; + } + return 0.0; +} + +/* +:returns: The mode of an established link. +*/ +link_mode Link::get_mode() { + assert(_object); + return _object->_mode; +} + +const Bytes& Link::get_salt() { + assert(_object); + return _object->_link_id; +} + +const Bytes Link::get_context() { + return {Bytes::NONE}; +} + +/* +:returns: The time in seconds since this link was established. +*/ +double Link::get_age() { + assert(_object); + if (_object->_activated_at) { + return OS::time() - _object->_activated_at; + } + return 0.0; +} + +/* +:returns: The time in seconds since last inbound packet on the link. This includes keepalive packets. +*/ +double Link::no_inbound_for() { + assert(_object); + //p activated_at = _object->_activated_at if _object->_activated_at != None else 0 + double activated_at = _object->_activated_at; + double last_inbound = std::max(_object->_last_inbound, activated_at); + return (OS::time() - last_inbound); +} + +/* +:returns: The time in seconds since last outbound packet on the link. This includes keepalive packets. +*/ +double Link::no_outbound_for() { + assert(_object); + return OS::time() - _object->_last_outbound; +} + +/* +:returns: The time in seconds since payload data traversed the link. This excludes keepalive packets. +*/ +double Link::no_data_for() { + assert(_object); + return OS::time() - _object->_last_data; +} + +/* +:returns: The time in seconds since activity on the link. This includes keepalive packets. +*/ +double Link::inactive_for() { + assert(_object); + return std::min(no_inbound_for(), no_outbound_for()); +} + +/* +:returns: The identity of the remote peer, if it is known. Calling this method will not query the remote initiator to reveal its identity. Returns ``None`` if the link initiator has not already independently called the ``identify(identity)`` method. +*/ +const Identity& Link::get_remote_identity() { + assert(_object); + return _object->__remote_identity; +} + +void Link::had_outbound(bool is_keepalive /*= false*/) { + assert(_object); + _object->_last_outbound = OS::time(); + if (!is_keepalive) { + _object->_last_data = _object->_last_outbound; + } +} + +/* +Closes the link and purges encryption keys. New keys will +be used if a new link to the same destination is established. +*/ +void Link::teardown() { + assert(_object); + if (_object->_status != Type::Link::PENDING && _object->_status != Type::Link::CLOSED) { + Packet teardown_packet(*this, _object->_link_id, Type::Packet::DATA, Type::Packet::LINKCLOSE); + teardown_packet.send(); + had_outbound(); + } + _object->_status = Type::Link::CLOSED; + if (_object->_initiator) { + _object->_teardown_reason = Type::Link::INITIATOR_CLOSED; + } + else { + _object->_teardown_reason = Type::Link::DESTINATION_CLOSED; + } + link_closed(); +} + +void Link::teardown_packet(const Packet& packet) { + assert(_object); + try { + Bytes plaintext = decrypt(packet.data()); + if (plaintext == _object->_link_id) { + _object->_status = Type::Link::CLOSED; + if (_object->_initiator) { + _object->_teardown_reason = Type::Link::DESTINATION_CLOSED; + } + else { + _object->_teardown_reason = Type::Link::INITIATOR_CLOSED; + } + link_closed(); + } + } + catch (std::exception& e) { + } +} + +void Link::link_closed() { + assert(_object); + for (auto& resource : _object->_incoming_resources) { + const_cast(resource).cancel(); + } + for (auto& resource : _object->_outgoing_resources) { + const_cast(resource).cancel(); + } + if (_object->_channel) { + _object->_channel._shutdown(); + } + + _object->_prv.reset(); + _object->_pub.reset(); + _object->_pub_bytes.clear(); + _object->_shared_key.clear(); + _object->_derived_key.clear(); + + if (_object->_destination) { + if (_object->_destination.direction() == Type::Destination::IN) { + if (_object->_destination.has_link(*this)) { + _object->_destination.remove_link(*this); + } + } + } + + if (_object->_callbacks._closed) { + try { + _object->_callbacks._closed(*this); + } + catch (std::exception& e) { + ERRORF("Error while executing link closed callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } +} + +// CBA TODO Implement watchdog +void Link::start_watchdog() { + //z thread = threading.Thread(target=_object->___watchdog_job) + //z thread.daemon = True + //z thread.start() +} + +/*p TODO + +void Link::__watchdog_job() { + assert(_object); + while not _object->_status == Type::Link::CLOSED: + while (_object->_watchdog_lock): + rtt_wait = 0.025 + if hasattr(self, "rtt") and _object->_rtt: + rtt_wait = _object->_rtt + + sleep(max(rtt_wait, 0.025)) + + if not _object->_status == Type::Link::CLOSED: + # Link was initiated, but no response + # from destination yet + if _object->_status == PENDING: + next_check = _object->_request_time + _object->_establishment_timeout + sleep_time = next_check - OS::time() + if OS::time() >= _object->_request_time + _object->_establishment_timeout: + RNS.log("Link establishment timed out", RNS.LOG_VERBOSE) + _object->_status = Type::Link::CLOSED + _object->_teardown_reason = TIMEOUT + link_closed() + sleep_time = 0.001 + + elif _object->_status == Type::Link::HANDSHAKE: + next_check = _object->_request_time + _object->_establishment_timeout + sleep_time = next_check - OS::time() + if OS::time() >= _object->_request_time + _object->_establishment_timeout: + _object->_status = Type::Link::CLOSED + _object->_teardown_reason = TIMEOUT + link_closed() + sleep_time = 0.001 + + if _object->_initiator: + RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG) + else: + RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG) + + elif _object->_status == Type::Link::ACTIVE: + activated_at = _object->_activated_at if _object->_activated_at != None else 0 + last_inbound = max(max(_object->_last_inbound, _object->_last_proof), activated_at) + + if OS::time() >= last_inbound + _object->_keepalive: + if _object->_initiator: + send_keepalive() + + if OS::time() >= last_inbound + _object->_stale_time: + sleep_time = _object->_rtt * _object->_keepalive_timeout_factor + STALE_GRACE + _object->_status = STALE + else: + sleep_time = _object->_keepalive + + else: + sleep_time = (last_inbound + _object->_keepalive) - OS::time() + + elif _object->_status == STALE: + sleep_time = 0.001 + _object->_status = Type::Link::CLOSED + _object->_teardown_reason = TIMEOUT + link_closed() + + + if sleep_time == 0: + RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_ERROR) + if sleep_time == None or sleep_time < 0: + RNS.log("Timing error! Tearing down link "+str(self)+" now.", RNS.LOG_ERROR) + teardown() + sleep_time = 0.1 + + sleep(sleep_time) + +*/ + +void Link::send_keepalive() { + assert(_object); + //p keepalive_packet = RNS.Packet(self, bytes([0xFF]), context=RNS.Packet.KEEPALIVE) + RNS::Packet keepalive_packet(*this, Bytes("\xFF"), Type::Packet::DATA, Type::Packet::KEEPALIVE); + keepalive_packet.send(); + had_outbound(true); +} + +void Link::handle_request(const Bytes& request_id, const ResourceRequest& resource_request) { + assert(_object); + DEBUGF("Link %s handling request", link_id().toHex().c_str()); + if (_object->_status == Type::Link::ACTIVE) { + //p requested_at = unpacked_request[0] + //p path_hash = unpacked_request[1] + //p request_data = unpacked_request[2] + + auto handler_iter = _object->_destination.request_handlers().find(resource_request._path_hash); + if (handler_iter != _object->_destination.request_handlers().end()) { + TRACE("Link::handle_request: Found handler"); + RequestHandler request_handler = (*handler_iter).second; + + bool allowed = false; + if (request_handler._allow != Type::Destination::ALLOW_NONE) { + if (request_handler._allow == Type::Destination::ALLOW_LIST) { + if (_object->__remote_identity && request_handler._allowed_list.count(_object->__remote_identity.hash()) > 0) { + allowed = true; + } + } + else if (request_handler._allow == Type::Destination::ALLOW_ALL) { + allowed = true; + } + } + + if (allowed) { + DEBUGF("Handling request %s for: %s", request_id.toHex().c_str(), request_handler._path.toString().c_str()); + //p if len(inspect.signature(response_generator).parameters) == 5: + //p response = response_generator(path, request_data, request_id, _object->__remote_identity, requested_at) + //p elif len(inspect.signature(response_generator).parameters) == 6: + //p response = response_generator(path, request_data, request_id, _object->_link_id, _object->__remote_identity, requested_at) + //p else: + //p raise TypeError("Invalid signature for response generator callback") + Bytes response(request_handler._response_generator(request_handler._path, resource_request._request_data, request_id, _object->_link_id, _object->__remote_identity, resource_request._requested_at)); + + if (response) { + //p packed_response = umsgpack.packb([request_id, response]) + MsgPack::Packer packer; + packer.to_array(request_id, response); + Bytes packed_response(packer.data(), packer.size()); + + if (packed_response.size() <= MDU) { + //p RNS.Packet(self, packed_response, Type::Packet::DATA, context = Type::Packet::RESPONSE).send() + RNS::Packet response_packet(*this, packed_response, Type::Packet::DATA, Type::Packet::RESPONSE); + response_packet.send(); + } + else { + // CBA TODO Determine why unused Resource is created here + Resource response_resource = RNS::Resource(packed_response, *this, request_id, true); + } + } + } + else { + std::string identity_string; + if (get_remote_identity()) { + identity_string = get_remote_identity().toString(); + } + else { + identity_string = ""; + } + DEBUGF("Request %s from %s not allowed for: %s", request_id.toHex().c_str(), identity_string.c_str(), request_handler._path.toString().c_str()); + } + } + } +} + +void Link::handle_response(const Bytes& request_id, const Bytes& response_data, size_t response_size, size_t response_transfer_size) { + assert(_object); + if (_object->_status == Type::Link::ACTIVE) { + RNS::RequestReceipt remove = {Type::NONE}; + for (RNS::RequestReceipt pending_request : _object->_pending_requests) { + if (pending_request.request_id() == request_id) { + remove = pending_request; + try { + pending_request.response_size(response_size); + //if (pending_request.response_transfer_size == 0) { + // pending_request.response_transfer_size = 0; + //} + pending_request.response_transfer_size(pending_request.response_transfer_size() + response_transfer_size); + pending_request.response_received(response_data); + } + catch (std::exception& e) { + ERRORF("Error occurred while handling response. The contained exception was: %s", e.what()); + } + break; + } + } + if (remove) { + if (_object->_pending_requests.count(remove) > 0) { + _object->_pending_requests.erase(remove); + } + } + } +} + +void Link::request_resource_concluded(const Resource& resource) { + assert(_object); + if (resource.status() == Type::Resource::COMPLETE) { + //p packed_request = resource.data().read() + Bytes packed_request = resource.data(); + //p unpacked_request = umsgpack.unpackb(packed_request) + MsgPack::Unpacker unpacker; + unpacker.feed(packed_request.data(), packed_request.size()); + // CBA TODO OPTIMIZE MSGPACK + double requested_at; + MsgPack::bin_t path_hash; + MsgPack::bin_t request_data; + unpacker.from_array(requested_at, path_hash, request_data); + ResourceRequest resource_request; + resource_request._requested_at = requested_at; + resource_request._path_hash = path_hash; + resource_request._request_data = request_data; + //p request_id = RNS.Identity.truncated_hash(packed_request) + Bytes request_id(Identity::truncated_hash(resource.data())); + //p request_data = unpacked_request + + //p handle_request(request_id, request_data) + handle_request(request_id, resource_request); + } + else { + DEBUGF("Incoming request resource failed with status: %d", resource.status()); + } +} + +void Link::response_resource_concluded(const Resource& resource) { + assert(_object); + if (resource.status() == Type::Resource::COMPLETE) { + //p packed_response = resource.data.read() + Bytes packed_response = resource.data(); + //p unpacked_response = umsgpack.unpackb(packed_response) + //p request_id = unpacked_response[0] + //p response_data = unpacked_response[1] + MsgPack::Unpacker unpacker; + unpacker.feed(packed_response.data(), packed_response.size()); + MsgPack::bin_t request_id; + MsgPack::bin_t response_data; + unpacker.from_array(request_id, response_data); + + handle_response(request_id, response_data, resource.total_size(), resource.size()); + } + else { + DEBUGF("Incoming response resource failed with status: %d", resource.status()); + for (RNS::RequestReceipt pending_request : _object->_pending_requests) { + if (pending_request.request_id() == resource.request_id()) { + pending_request.request_timed_out({Type::NONE}); + } + } + } +} + + +/*z +""" +Get the ``Channel`` for this link. + +:return: ``Channel`` object +""" +void Link::get_channel() { + assert(_object); + if _object->_channel is None: + _object->_channel = Channel(LinkChannelOutlet(self)) + return _object->_channel +*/ + +/* +void Link::receive(const Packet& packet) { +} +*/ +void Link::receive(const Packet& packet) { + assert(_object); + _object->_watchdog_lock = true; + if (_object->_status != Type::Link::CLOSED && !(_object->_initiator && packet.context() == Type::Packet::KEEPALIVE && packet.data() == "\xFF")) { + if (packet.receiving_interface() != _object->_attached_interface) { + ERROR("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!"); + } + else { + _object->_last_inbound = OS::time(); + if (packet.context() != Type::Packet::KEEPALIVE) { + _object->_last_data = _object->_last_inbound; + } + _object->_rx += 1; + _object->_rxbytes += packet.data().size(); + if (_object->_status == STALE) { + _object->_status = Type::Link::ACTIVE; + } + + if (packet.packet_type() == Type::Packet::DATA) { + bool should_query = false; + switch (packet.context()) { + case Type::Packet::CONTEXT_NONE: + { + const Bytes plaintext = decrypt(packet.data()); + if (plaintext) { + if (_object->_callbacks._packet) { + //z thread = threading.Thread(target=_object->_callbacks.packet, args=(plaintext, packet)) + //z thread.daemon = True + //z thread.start() + try { + _object->_callbacks._packet(plaintext, packet); + } + catch (std::exception& e) { + ERRORF("Error while executing packet callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + + if (_object->_destination.proof_strategy() == Type::Destination::PROVE_ALL) { + const_cast(packet).prove(); + should_query = true; + } + else if (_object->_destination.proof_strategy() == Type::Destination::PROVE_APP) { + if (_object->_destination.callbacks()._proof_requested) { + try { + if (_object->_destination.callbacks()._proof_requested(packet)) { + const_cast(packet).prove(); + should_query = true; + } + } + catch (std::exception& e) { + ERRORF("Error while executing proof request callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + } + } + break; + } + case Type::Packet::LINKIDENTIFY: + { + const Bytes plaintext = decrypt(packet.data()); + if (plaintext) { + if (!(_object->_initiator) && plaintext.size() == Type::Identity::KEYSIZE/8 + Type::Identity::SIGLENGTH/8) { + const Bytes public_key = plaintext.left(Type::Identity::KEYSIZE/8); + const Bytes signed_data = _object->_link_id + public_key; + const Bytes signature = plaintext.mid(Type::Identity::KEYSIZE/8, Type::Identity::SIGLENGTH/8); + Identity identity(false); + identity.load_public_key(public_key); + + if (identity.validate(signature, signed_data)) { + _object->__remote_identity = identity; + if (_object->_callbacks._remote_identified) { + try { + _object->_callbacks._remote_identified(*this, _object->__remote_identity); + } + catch (std::exception& e) { + ERRORF("Error while executing remote identified callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + } + } + } + break; + } + case Type::Packet::REQUEST: + { + try { + const Bytes request_id = packet.getTruncatedHash(); + const Bytes packed_request = decrypt(packet.data()); + if (packed_request) { + //p unpacked_request = umsgpack.unpackb(packed_request) + MsgPack::Unpacker unpacker; + unpacker.feed(packed_request.data(), packed_request.size()); + // CBA TODO OPTIMIZE MSGPACK + double requested_at; + MsgPack::bin_t path_hash; + MsgPack::bin_t request_data; + unpacker.from_array(requested_at, path_hash, request_data); + ResourceRequest resource_request; + resource_request._requested_at = requested_at; + resource_request._path_hash = path_hash; + resource_request._request_data = request_data; + handle_request(request_id, resource_request); + } + } + catch (std::exception& e) { + ERRORF("Error occurred while handling request. The contained exception was: %s", e.what()); + } + break; + } + case Type::Packet::RESPONSE: + { + try { + const Bytes packed_response = decrypt(packet.data()); + if (packed_response) { + //p unpacked_response = umsgpack.unpackb(packed_response) + //p request_id = unpacked_response[0] + //p response_data = unpacked_response[1] + //p transfer_size = len(umsgpack.packb(response_data))-2 + MsgPack::Unpacker unpacker; + unpacker.feed(packed_response.data(), packed_response.size()); + MsgPack::bin_t request_id; + MsgPack::bin_t response_data; + unpacker.from_array(request_id, response_data); + MsgPack::Packer packer; + packer.serialize(response_data); + size_t transfer_size = packer.size() - 2; + handle_response(Bytes(request_id.data(), request_id.size()), Bytes(response_data.data(), response_data.size()), transfer_size, transfer_size); + } + } + catch (std::exception& e) { + ERRORF("Error occurred while handling response. The contained exception was: %s", e.what()); + } + break; + } + case Type::Packet::LRRTT: + { + if (!_object->_initiator) { + rtt_packet(packet); + } + } + case Type::Packet::LINKCLOSE: + { + teardown_packet(packet); + break; + } +/*z + case Type::Packet::RESOURCE_ADV: + { + //p packet.plaintext = decrypt(packet.data) + const Bytes plaintext = decrypt(packet.data()); + if (plaintext) { + const_cast(packet).plaintext(plaintext); + if (ResourceAdvertisement::is_request(packet)) { + Resource::accept(packet, callback=_object->_request_resource_concluded); + } + else if (ResourceAdvertisement::is_response(packet)) { + Bytes request_id = ResourceAdvertisement::read_request_id(packet) + for (auto& pending_request : _object->_pending_requests) { + if (pending_request.request_id == request_id) { + const Bytes response_resource = Resource::accept(packet, callback=_object->_response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id); + if (response_resource) { + //p if pending_request.response_size == None: + if (pending_request.response_size == 0) { + pending_request.response_size = ResourceAdvertisement::read_size(packet); + } + //p if pending_request.response_transfer_size == None: + if (pending_request.response_transfer_size == 0) { + pending_request.response_transfer_size = 0; + } + pending_request.response_transfer_size += ResourceAdvertisement::read_transfer_size(packet); + //p if pending_request.started_at == None: + if (pending_request.started_at == 0.0) { + pending_request.started_at = OS::time(); + } + pending_request.response_resource_progress(response_resource); + } + } + } + } + else if (_object->_resource_strategy == ACCEPT_NONE) { + //p pass + } + else if (_object->_resource_strategy == ACCEPT_APP) { + if (_object->_callbacks.resource) { + try { + resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext()); + resource_advertisement.link = *this; + if (_object->_callbacks.resource(resource_advertisement)) { + Resource::accept(packet, _object->_callbacks.resource_concluded); + } + } + catch (std::exception& e) { + ERRORF("Error while executing resource accept callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + elif _object->_resource_strategy == ACCEPT_ALL: + RNS.Resource.accept(packet, _object->_callbacks.resource_concluded) + break; + } + case Type::Packet::RESOURCE_REQ: + { + const Bytes plaintext = decrypt(packet.data()); + if (plaintext) { + if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED: + resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:Type::Identity::HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN] + else: + resource_hash = plaintext[1:Type::Identity::HASHLENGTH//8+1] + + for resource in _object->_outgoing_resources: + if resource.hash == resource_hash: + // We need to check that this request has not been + // received before in order to avoid sequencing errors. + if not packet.packet_hash in resource.req_hashlist: + resource.req_hashlist.append(packet.packet_hash) + resource.request(plaintext) + break; + } + case Type::Packet::RESOURCE_HMU: + { + const Bytes plaintext = decrypt(packet.data()); + if (plaintext) { + resource_hash = plaintext[:Type::Identity::HASHLENGTH//8] + for resource in _object->_incoming_resources: + if resource_hash == resource.hash: + resource.hashmap_update_packet(plaintext) + break; + } + case Type::Packet::RESOURCE_ICL: + { + const Bytes plaintext = decrypt(packet.data()); + if (plaintext) { + resource_hash = plaintext[:Type::Identity::HASHLENGTH//8] + for resource in _object->_incoming_resources: + if resource_hash == resource.hash: + resource.cancel() + break; + } +*/ + case Type::Packet::KEEPALIVE: + { + if (!_object->_initiator && packet.data() == "\xFF") { + //p keepalive_packet = RNS.Packet(self, bytes([0xFE]), context=RNS.Packet.KEEPALIVE) + Packet keepalive_packet(*this, Bytes("\xFE"), Type::Packet::DATA, Type::Packet::KEEPALIVE); + keepalive_packet.send(); + had_outbound(true); + } + break; + } + // TODO: find the most efficient way to allow multiple + // transfers at the same time, sending resource hash on + // each packet is a huge overhead. Probably some kind + // of hash -> sequence map + case Type::Packet::RESOURCE: + { + for (auto& resource : _object->_incoming_resources) { + //z resource.receive_part(packet); + } + break; + } +/*z + case Type::Packet::CHANNEL: + { + //z if (!_object->_channel) { + if (true) { + DEBUG(f"Channel data received without open channel") + } + else { + //z packet.prove(); + //z plaintext = decrypt(packet.data()); + //z if (plaintext) { + //z _object->_channel._receive(plaintext); + //z } + } + break; + } +*/ + } + } + else if (packet.packet_type() == Type::Packet::PROOF) { + if (packet.context() == Type::Packet::RESOURCE_PRF) { + Bytes resource_hash = packet.data().left(Type::Identity::HASHLENGTH/8); + for (const auto& resource : _object->_outgoing_resources) { + if (resource_hash == resource.hash()) { + //z resource.validate_proof(packet.data()); + } + } + } + } + } + } + + _object->_watchdog_lock = false; +} + +const Bytes Link::encrypt(const Bytes& plaintext) { + assert(_object); + TRACE("Link::encrypt: encrypting data..."); + try { + if (!_object->_token) { + try { + _object->_token.reset(new Token(_object->_derived_key)); + } + catch (std::exception& e) { + ERRORF("Could not instantiate Token while performing encryption on link %s. The contained exception was: %s", toString().c_str(), e.what()); + throw e; + } + } + return _object->_token->encrypt(plaintext); + } + catch (std::exception& e) { + ERRORF("Encryption on link %s failed. The contained exception was: %s", toString().c_str(), e.what()); + throw e; + } +} + +const Bytes Link::decrypt(const Bytes& ciphertext) { + assert(_object); + TRACE("Link::decrypt: decrypting data..."); + try { + if (!_object->_token) { + _object->_token.reset(new Token(_object->_derived_key)); + } + return _object->_token->decrypt(ciphertext); + } + catch (std::exception& e) { + ERRORF("Decryption failed on link %s. The contained exception was: %s", toString().c_str(), e.what()); + return {Bytes::NONE}; + } +} + +const Bytes Link::sign(const Bytes& message) { + assert(_object); + return _object->_sig_prv->sign(message); +} + +bool Link::validate(const Bytes& signature, const Bytes& message) { + assert(_object); + try { + _object->_peer_sig_pub->verify(signature, message); + return true; + } + catch (std::exception& e) { + return false; + } +} + +void Link::set_link_established_callback(Callbacks::established callback) { + assert(_object); + _object->_callbacks._established = callback; +} + +void Link::set_link_closed_callback(Callbacks::closed callback) { + assert(_object); + _object->_callbacks._closed = callback; +} + +void Link::set_packet_callback(Callbacks::packet callback) { + assert(_object); + _object->_callbacks._packet = callback; +} + +void Link::set_remote_identified_callback(Callbacks::remote_identified callback) { + assert(_object); + _object->_callbacks._remote_identified = callback; +} + +void Link::set_resource_callback(Callbacks::resource callback) { + assert(_object); + _object->_callbacks._resource = callback; +} + +void Link::set_resource_started_callback(Callbacks::resource_started callback) { + assert(_object); + _object->_callbacks._resource_started = callback; +} + +void Link::set_resource_concluded_callback(Callbacks::resource_concluded callback) { + assert(_object); + _object->_callbacks._resource_concluded = callback; +} + + +void Link::resource_concluded(const Resource& resource) { + assert(_object); + if (_object->_incoming_resources.count(resource) > 0) { + _object->_incoming_resources.erase(resource); + } + if (_object->_outgoing_resources.count(resource) > 0) { + _object->_outgoing_resources.erase(resource); + } +} + +/* +Sets the resource strategy for the link. + +:param resource_strategy: One of ``RNS.ACCEPT_NONE``, ``RNS.ACCEPT_ALL`` or ``RNS.ACCEPT_APP``. If ``RNS.ACCEPT_APP`` is set, the `resource_callback` will be called to determine whether the resource should be accepted or not. +:raises: *TypeError* if the resource strategy is unsupported. +*/ +void Link::set_resource_strategy(resource_strategy strategy) { + assert(_object); + if (!(strategy & resource_strategies)) { + throw std::runtime_error("Unsupported resource strategy"); + } + else { + _object->_resource_strategy = strategy; + } +} + +void Link::register_outgoing_resource(const Resource& resource) { + assert(_object); + _object->_outgoing_resources.insert(resource); +} + +void Link::register_incoming_resource(const Resource& resource) { + assert(_object); + _object->_incoming_resources.insert(resource); +} + +bool Link::has_incoming_resource(const Resource& resource) { + assert(_object); + for (const auto& incoming_resource : _object->_incoming_resources) { + if (incoming_resource.hash() == resource.hash()) { + return true; + } + } + return false; +} + +void Link::cancel_outgoing_resource(const Resource& resource) { + assert(_object); + if (_object->_outgoing_resources.count(resource) > 0) { + _object->_outgoing_resources.erase(resource); + } + else { + ERROR("Attempt to cancel a non-existing outgoing resource"); + } +} + +void Link::cancel_incoming_resource(const Resource& resource) { + assert(_object); + if (_object->_incoming_resources.count(resource)) { + _object->_incoming_resources.erase(resource); + } + else { + ERROR("Attempt to cancel a non-existing incoming resource"); + } +} + +bool Link::ready_for_new_resource() { + assert(_object); + return (_object->_outgoing_resources.size() > 0); +} + +std::string Link::toString() const { + if (!_object) { + return ""; + } + return "{Link:" + _object->_link_id.toHex() + "}"; +} + +// getters + +double Link::rtt() const { + assert(_object); + return _object->_rtt; +} + +const Destination& Link::destination() const { + assert(_object); + return _object->_destination; +} + +// CBA LINK +/* +const Destination& Link::link_destination() const { + assert(_object); + return _object->_link_destination; +} +*/ + +const Bytes& Link::link_id() const { + assert(_object); + return _object->_link_id; +} + +const Bytes& Link::hash() const { + assert(_object); + return _object->_hash; +} + +uint16_t Link::mtu() const { + assert(_object); + return _object->_mtu; +} + +Type::Link::status Link::status() const { + assert(_object); + return _object->_status; +} + +double Link::establishment_timeout() const { + assert(_object); + return _object->_establishment_timeout; +} + +uint16_t Link::establishment_cost() const { + assert(_object); + return _object->_establishment_cost; +} + +uint8_t Link::traffic_timeout_factor() const { + assert(_object); + return _object->_traffic_timeout_factor; +} + +double Link::request_time() const { + assert(_object); + return _object->_request_time; +} + +double Link::last_inbound() const { + assert(_object); + return _object->_last_inbound; +} + +std::set& Link::pending_requests() const { + assert(_object); + return _object->_pending_requests; +} + +Type::Link::teardown_reason Link::teardown_reason() const { + assert(_object); + return _object->_teardown_reason; +} + +bool Link::initiator() const { + assert(_object); + return _object->_initiator; +} + +// setters + +void Link::destination(const Destination& destination) { + assert(_object); + _object->_destination = destination; +} + +void Link::attached_interface(const Interface& interface) { + assert(_object); + _object->_attached_interface = interface; +} + +void Link::establishment_timeout(double timeout) { + assert(_object); + _object->_establishment_timeout = timeout; +} + +void Link::establishment_cost(uint16_t cost) { + assert(_object); + _object->_establishment_cost = cost; +} + +void Link::request_time(double time) { + assert(_object); + _object->_request_time = time; +} + +void Link::last_inbound(double time) { + assert(_object); + _object->_last_inbound = time; +} + +void Link::last_outbound(double time) { + assert(_object); + _object->_last_outbound = time; +} + +void Link::increment_tx() { + assert(_object); + _object->_tx++; +} + +void Link::increment_txbytes(uint16_t bytes) { + assert(_object); + _object->_txbytes += bytes; +} + +void Link::status(Type::Link::status status) { + assert(_object); + _object->_status = status; +} + +void Link::mtu(uint16_t mtu) { + assert(_object); + _object->_mtu = mtu; +} + +void Link::mode(RNS::Type::Link::link_mode mode) { + assert(_object); + _object->_mode = mode; +} + + +//RequestReceipt::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::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*/) : + _object(new RequestReceiptData()) +{ + assert(_object); + _object->_packet_receipt = packet_receipt; + _object->_resource = resource; + if (_object->_packet_receipt) { + _object->_hash = packet_receipt.truncated_hash(); + //z _object->_packet_receipt.set_timeout_callback(request_timed_out); + _object->_started_at = OS::time(); + } + else if (_object->_resource) { + _object->_hash = resource.request_id(); + // CBA TODO Need to find cross-platform way of passing class method as callback + //z const_cast(resource).set_concluded_callback(request_resource_concluded); + //z const_cast(resource).set_concluded_callback(std::bind(&RequestReceipt::request_resource_concluded, this, std::placeholders::_1)); + } + _object->_link = link; + _object->_request_id = _object->_hash; + _object->_request_size = request_size; + _object->_sent_at = OS::time(); + if (timeout != 0.0) { + _object->_timeout = timeout; + } + else { + throw new std::invalid_argument("No timeout specified for request receipt"); + } + + _object->_callbacks._response = response_callback; + _object->_callbacks._failed = failed_callback; + _object->_callbacks._progress = progress_callback; + + _object->_link.pending_requests().insert(*this); +} + +void RequestReceipt::request_resource_concluded(const Resource& resource) { + assert(_object); + if (resource.status() == Type::Resource::COMPLETE) { + DEBUGF("Request %s successfully sent as resource.", _object->_request_id.toHex().c_str()); + if (_object->_started_at == 0.0) { + _object->_started_at = OS::time(); + } + _object->_status = Type::RequestReceipt::DELIVERED; + _object->_resource_response_timeout = OS::time() + _object->_timeout; + //p response_timeout_thread = threading.Thread(target=_object->___response_timeout_job) + //p response_timeout_thread.daemon = True + //p response_timeout_thread.start() + } + else { + DEBUGF("Sending request %s as resource failed with status: %d", _object->_request_id.toHex().c_str(), resource.status()); + _object->_status = Type::RequestReceipt::FAILED; + _object->_concluded_at = OS::time(); + _object->_link.pending_requests().erase(*this); + if (_object->_callbacks._failed != nullptr) { + try { + _object->_callbacks._failed(*this); + } + catch (std::exception& e) { + ERRORF("Error while executing request failed callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + } +} + + +void RequestReceipt::__response_timeout_job() { + assert(_object); + while (_object->_status == Type::RequestReceipt::DELIVERED) { + double now = OS::time(); + if (now > _object->___resource_response_timeout) { + request_timed_out({Type::NONE}); + } + + OS::sleep(0.1); + } +} + + +void RequestReceipt::request_timed_out(const PacketReceipt& packet_receipt) { + assert(_object); + _object->_status = Type::RequestReceipt::FAILED; + _object->_concluded_at = OS::time(); + _object->_link.pending_requests().erase(*this); + + if (_object->_callbacks._failed != nullptr) { + try { + _object->_callbacks._failed(*this); + } + catch (std::exception& e) { + ERRORF("Error while executing request timed out callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } +} + + +void RequestReceipt::response_resource_progress(const Resource& resource) { + assert(_object); + if (resource) { + if (_object->_status != Type::RequestReceipt::FAILED) { + _object->_status = Type::RequestReceipt::RECEIVING; + if (_object->_packet_receipt) { + if (_object->_packet_receipt.status() != Type::PacketReceipt::DELIVERED) { + _object->_packet_receipt.status(Type::PacketReceipt::DELIVERED); + _object->_packet_receipt.proved(true); + _object->_packet_receipt.concluded_at(OS::time()); + if (_object->_packet_receipt.callbacks()._delivery != nullptr) { + _object->_packet_receipt.callbacks()._delivery(_object->_packet_receipt); + } + } + } + + _object->_progress = resource.get_progress(); + + if (_object->_callbacks._progress != nullptr) { + try { + _object->_callbacks._progress(*this); + } + catch (std::exception& e) { + ERRORF("Error while executing response progress callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + } + else { + const_cast(resource).cancel(); + } + } +} + +void RequestReceipt::response_received(const Bytes& response) { + assert(_object); + if (_object->_status != Type::RequestReceipt::FAILED) { + _object->_progress = 1.0; + _object->_response = response; + _object->_status = Type::RequestReceipt::READY; + _object->_response_concluded_at = OS::time(); + + if (_object->_packet_receipt) { + _object->_packet_receipt.status(Type::PacketReceipt::DELIVERED); + _object->_packet_receipt.proved(true); + _object->_packet_receipt.concluded_at(OS::time()); + if (_object->_packet_receipt.callbacks()._delivery != nullptr) { + _object->_packet_receipt.callbacks()._delivery(_object->_packet_receipt); + } + } + if (_object->_callbacks._progress != nullptr) { + try { + _object->_callbacks._progress(*this); + } + catch (std::exception& e) { + ERRORF("Error while executing response progress callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + if (_object->_callbacks._response != nullptr) { + try { + _object->_callbacks._response(*this); + } + catch (std::exception& e) { + ERRORF("Error while executing response received callback from %s. The contained exception was: %s", toString().c_str(), e.what()); + } + } + } +} + +// :returns: The request ID as *bytes*. +const Bytes& RequestReceipt::get_request_id() const { + assert(_object); + return _object->_request_id; +} + +// :returns: The current status of the request, one of ``RNS.RequestReceipt.FAILED``, ``RNS.RequestReceipt.SENT``, ``RNS.RequestReceipt.DELIVERED``, ``RNS.RequestReceipt.READY``. +Type::RequestReceipt::status RequestReceipt::get_status() const { + assert(_object); + return _object->_status; +} + +// :returns: The progress of a response being received as a *float* between 0.0 and 1.0. +float RequestReceipt::get_progress() const { + assert(_object); + return _object->_progress; +} + +// :returns: The response as *bytes* if it is ready, otherwise *None*. +const Bytes RequestReceipt::get_response() const { + assert(_object); + if (_object->_status == Type::RequestReceipt::READY) { + return _object->_response; + } + else { + return Bytes::NONE; + } +} + +// :returns: The response time of the request in seconds. +double RequestReceipt::get_response_time() const { + assert(_object); + if (_object->_status == Type::RequestReceipt::READY) { + return _object->_response_concluded_at - _object->_started_at; + } + else { + return 0.0; + } +} + +std::string RequestReceipt::toString() const { + if (!_object) { + return ""; + } + return "{RequestReceipt:" + _object->_hash.toHex() + "}"; +} + +// getters + +const Bytes& RequestReceipt::hash() const { + assert(_object); + return _object->_hash; +} + +const Bytes& RequestReceipt::request_id() const { + assert(_object); + return _object->_request_id; +} + +size_t RequestReceipt::response_transfer_size() const { + assert(_object); + return _object->_response_transfer_size; +} + +// setters + +void RequestReceipt::response_size(size_t size) { + assert(_object); + _object->_response_size = size; +} + +void RequestReceipt::response_transfer_size(size_t size) { + assert(_object); + _object->_response_transfer_size = size; +} diff --git a/lib/microReticulum/src/Link.h b/lib/microReticulum/src/Link.h new file mode 100755 index 0000000..8f02fe6 --- /dev/null +++ b/lib/microReticulum/src/Link.h @@ -0,0 +1,270 @@ +#pragma once + +#include "Destination.h" +#include "Type.h" + +#include +#include + +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 _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` 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 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& 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 _object; + + }; + +} diff --git a/lib/microReticulum/src/LinkData.h b/lib/microReticulum/src/LinkData.h new file mode 100755 index 0000000..18d7ddb --- /dev/null +++ b/lib/microReticulum/src/LinkData.h @@ -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 + +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 _incoming_resources; + std::set _outgoing_resources; + std::set _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; + }; + +} diff --git a/lib/microReticulum/src/Log.cpp b/lib/microReticulum/src/Log.cpp new file mode 100755 index 0000000..2da9a51 --- /dev/null +++ b/lib/microReticulum/src/Log.cpp @@ -0,0 +1,111 @@ +#include "Log.h" + +#include "Utilities/OS.h" + +#include +#include +#include + +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); +} diff --git a/lib/microReticulum/src/Log.h b/lib/microReticulum/src/Log.h new file mode 100755 index 0000000..5caacbb --- /dev/null +++ b/lib/microReticulum/src/Log.h @@ -0,0 +1,124 @@ +#pragma once + +#ifdef ARDUINO +#include +#endif + +#include + +#include + +#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); } + +} diff --git a/lib/microReticulum/src/Packet.cpp b/lib/microReticulum/src/Packet.cpp new file mode 100755 index 0000000..0e68a49 --- /dev/null +++ b/lib/microReticulum/src/Packet.cpp @@ -0,0 +1,995 @@ +#include "Packet.h" + +#include "Reticulum.h" +#include "Transport.h" +#include "Identity.h" +#include "Log.h" +#include "Utilities/OS.h" + +#include +#include + +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((flags & 0b01000000) >> 6); + _object->_context_flag = static_cast((flags & 0b00100000) >> 5); + _object->_transport_type = static_cast((flags & 0b00010000) >> 4); + _object->_destination_type = static_cast((flags & 0b00001100) >> 2); + _object->_packet_type = static_cast(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(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(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` 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` 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()); + // Query transport for matching interface + dst = Transport::get_cached_packet(hash); + } + else { + dst = {RNS::Type::NONE}; + } +} +*/ \ No newline at end of file diff --git a/lib/microReticulum/src/Packet.h b/lib/microReticulum/src/Packet.h new file mode 100755 index 0000000..c6898ef --- /dev/null +++ b/lib/microReticulum/src/Packet.h @@ -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 +#include +#include +#include + +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` 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` 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; + + }; + + + 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; + + }; + +} + +/* +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() && strlen(src.as()) == 64; + } +} +*/ +/* +namespace ArduinoJson { + template <> + struct Converter { + 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()); + 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() && strlen(src.as()) == 64; + } + }; +} +*/ diff --git a/lib/microReticulum/src/Resource.cpp b/lib/microReticulum/src/Resource.cpp new file mode 100755 index 0000000..cb4a653 --- /dev/null +++ b/lib/microReticulum/src/Resource.cpp @@ -0,0 +1,122 @@ +#include "Resource.h" + +#include "ResourceData.h" +#include "Reticulum.h" +#include "Transport.h" +#include "Packet.h" +#include "Log.h" + +#include + +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 + diff --git a/lib/microReticulum/src/Resource.h b/lib/microReticulum/src/Resource.h new file mode 100755 index 0000000..e1cd909 --- /dev/null +++ b/lib/microReticulum/src/Resource.h @@ -0,0 +1,110 @@ +#pragma once + +#include "Destination.h" +#include "Type.h" + +#include +#include + +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 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 _object; + + }; + + + class ResourceAdvertisement { + + }; + +} diff --git a/lib/microReticulum/src/ResourceData.h b/lib/microReticulum/src/ResourceData.h new file mode 100755 index 0000000..86cd18f --- /dev/null +++ b/lib/microReticulum/src/ResourceData.h @@ -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; + }; + +} diff --git a/lib/microReticulum/src/Reticulum.cpp b/lib/microReticulum/src/Reticulum.cpp new file mode 100755 index 0000000..0e49c1b --- /dev/null +++ b/lib/microReticulum/src/Reticulum.cpp @@ -0,0 +1,492 @@ +#include "Reticulum.h" + +#include "Transport.h" +#include "Log.h" + +//#include +#include + +#ifdef ARDUINO +#include +//#include +#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& 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& 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 +*/ diff --git a/lib/microReticulum/src/Reticulum.h b/lib/microReticulum/src/Reticulum.h new file mode 100755 index 0000000..6cd12af --- /dev/null +++ b/lib/microReticulum/src/Reticulum.h @@ -0,0 +1,182 @@ +#pragma once + +#include "Transport.h" +#include "Log.h" +#include "Type.h" +#include "Utilities/OS.h" + +#include +#include +#include +#include +#include +#include + +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& get_path_table() const; + const std::map& 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; + + }; + +} diff --git a/lib/microReticulum/src/Transport.cpp b/lib/microReticulum/src/Transport.cpp new file mode 100755 index 0000000..13ad1a0 --- /dev/null +++ b/lib/microReticulum/src/Transport.cpp @@ -0,0 +1,4305 @@ +#include "Transport.h" +#include "Link.h" + +#include "Reticulum.h" +#include "Destination.h" +#include "Identity.h" +#include "Packet.h" +#include "Interface.h" +#include "Log.h" +#include "Cryptography/Random.h" +#include "Utilities/OS.h" +#include "Utilities/Persistence.h" + +#include +#include +#include + +using namespace RNS; +using namespace RNS::Type::Transport; +using namespace RNS::Utilities; + +#if defined(INTERFACES_SET) +///*static*/ std::set, std::less> Transport::_interfaces; +/*static*/ std::set, std::less> Transport::_interfaces; +#elif defined(INTERFACES_LIST) +/*static*/ std::list> Transport::_interfaces; +#elif defined(INTERFACES_MAP) +/*static*/ std::map Transport::_interfaces; +#endif +#if defined(DESTINATIONS_SET) +/*static*/ std::set Transport::_destinations; +#elif defined(DESTINATIONS_MAP) +/*static*/ std::map Transport::_destinations; +#endif +/*static*/ std::set Transport::_pending_links; +/*static*/ std::set Transport::_active_links; +/*static*/ std::set Transport::_packet_hashlist; +/*static*/ std::list Transport::_receipts; + +/*static*/ std::map Transport::_announce_table; +/*static*/ std::map Transport::_destination_table; +/*static*/ std::map Transport::_reverse_table; +/*static*/ std::map Transport::_link_table; +/*static*/ std::map Transport::_held_announces; +/*static*/ std::set Transport::_announce_handlers; +/*static*/ std::map Transport::_tunnels; +/*static*/ std::map Transport::_announce_rate_table; +/*static*/ std::map Transport::_path_requests; + +/*static*/ std::map Transport::_discovery_path_requests; +/*static*/ std::set Transport::_discovery_pr_tags; + +/*static*/ std::set Transport::_control_destinations; +/*static*/ std::set Transport::_control_hashes; + +///*static*/ std::set Transport::_local_client_interfaces; +/*static*/ std::set, std::less> Transport::_local_client_interfaces; + +/*static*/ std::map Transport::_pending_local_path_requests; + +// CBA +/*static*/ std::map Transport::_packet_table; + +/*static*/ uint16_t Transport::_LOCAL_CLIENT_CACHE_MAXSIZE = 512; + +/*static*/ double Transport::_start_time = 0.0; +/*static*/ bool Transport::_jobs_locked = false; +/*static*/ bool Transport::_jobs_running = false; +/*static*/ float Transport::_job_interval = 0.250; +/*static*/ double Transport::_jobs_last_run = 0.0; +/*static*/ double Transport::_links_last_checked = 0.0; +/*static*/ float Transport::_links_check_interval = 1.0; +/*static*/ double Transport::_receipts_last_checked = 0.0; +/*static*/ float Transport::_receipts_check_interval = 1.0; +/*static*/ double Transport::_announces_last_checked = 0.0; +/*static*/ float Transport::_announces_check_interval = 1.0; +/*static*/ double Transport::_tables_last_culled = 0.0; +// CBA MCU +/*static*/ //float Transport::_tables_cull_interval = 5.0; +/*static*/ float Transport::_tables_cull_interval = 60.0; +/*static*/ bool Transport::_saving_path_table = false; +// CBA ACCUMULATES +// CBA MCU +/*static*/ //uint16_t Transport::_hashlist_maxsize = 1000000; +/*static*/ //uint16_t Transport::_hashlist_maxsize = 100; +/*static*/ uint16_t Transport::_hashlist_maxsize = 100; +// CBA ACCUMULATES +// CBA MCU +/*static*/ //uint16_t Transport::_max_pr_tags = 32000; +/*static*/ uint16_t Transport::_max_pr_tags = 32; + +// CBA +// CBA ACCUMULATES +/*static*/ uint16_t Transport::_path_table_maxsize = 100; +// CBA ACCUMULATES +/*static*/ uint16_t Transport::_path_table_maxpersist = 100; +/*static*/ double Transport::_last_saved = 0.0; +/*static*/ float Transport::_save_interval = 3600.0; +/*static*/ uint32_t Transport::_destination_table_crc = 0; + +/*static*/ Reticulum Transport::_owner({Type::NONE}); + +// BOUNDARY MODE Whitelist 1: addresses of local devices (from LoRa and LocalTCP interfaces) +static std::set _boundary_local_addresses; +// BOUNDARY MODE Whitelist 2: addresses mentioned in packets from local devices +static std::set _boundary_mentioned_addresses; + +// BOUNDARY MODE: Check if an interface is the backbone +static bool is_backbone_interface(const Interface& iface) { + return iface.is_backbone(); +} +/*static*/ Identity Transport::_identity({Type::NONE}); + +// CBA +/*static*/ Transport::Callbacks Transport::_callbacks; + +// CBA Stats +/*static*/ uint32_t Transport::_packets_sent = 0; +/*static*/ uint32_t Transport::_packets_received = 0; +/*static*/ uint32_t Transport::_destinations_added = 0; +/*static*/ size_t Transport::_last_memory = 0; +/*static*/ size_t Transport::_last_flash = 0; + +/*static*/ void Transport::start(const Reticulum& reticulum_instance) { + INFO("Transport starting..."); + _jobs_running = true; + _owner = reticulum_instance; + + // Initialize time-based variables *after* time offset update + _jobs_last_run = OS::time(); + _links_last_checked = OS::time(); + _receipts_last_checked = OS::time(); + _announces_last_checked = OS::time(); + _tables_last_culled = OS::time(); + _last_saved = OS::time(); + + // ensure required directories exist + if (!OS::directory_exists(Reticulum::_cachepath)) { + VERBOSE("No cache directory, creating..."); + OS::create_directory(Reticulum::_cachepath); + } + + if (!_identity) { + char transport_identity_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(transport_identity_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/transport_identity", Reticulum::_storagepath); + DEBUG("Checking for transport identity..."); + try { + if (OS::file_exists(transport_identity_path)) { + _identity = Identity::from_file(transport_identity_path); + } + + if (!_identity) { + VERBOSE("No valid Transport Identity in storage, creating..."); + _identity = Identity(); + _identity.to_file(transport_identity_path); + } + else { + VERBOSE("Loaded Transport Identity from storage"); + } + } + catch (std::exception& e) { + ERRORF("Failed to check for transport identity, the contained exception was: %s", e.what()); + } + } + +// TODO +/* + packet_hashlist_path = Reticulum::storagepath + "/packet_hashlist"; + if (!owner.is_connected_to_shared_instance()) { + if (os.path.isfile(packet_hashlist_path)) { + try { + //p file = open(packet_hashlist_path, "rb") + //p Transport.packet_hashlist = umsgpack.unpackb(file.read()) + //p file.close() + } + catch (std::exception& e) { + ERRORF("Could not load packet hashlist from storage, the contained exception was: %s", e.what()); + } + } + } +*/ + + // Create transport-specific destination for path request + Destination path_request_destination({Type::NONE}, Type::Destination::IN, Type::Destination::PLAIN, APP_NAME, "path.request"); + path_request_destination.set_packet_callback(path_request_handler); + // CBA ACCUMULATES + _control_destinations.insert(path_request_destination); + // CBA ACCUMULATES + _control_hashes.insert(path_request_destination.hash()); + DEBUG("Created transport-specific path request destination " + path_request_destination.hash().toHex()); + + // Create transport-specific destination for tunnel synthesize + Destination tunnel_synthesize_destination({Type::NONE}, Type::Destination::IN, Type::Destination::PLAIN, APP_NAME, "tunnel.synthesize"); + tunnel_synthesize_destination.set_packet_callback(tunnel_synthesize_handler); + // CBA BUG? + //p Transport.control_destinations.append(Transport.tunnel_synthesize_handler) + // CBA ACCUMULATES + _control_destinations.insert(tunnel_synthesize_destination); + // CBA ACCUMULATES + _control_hashes.insert(tunnel_synthesize_destination.hash()); + DEBUG("Created transport-specific tunnel synthesize destination " + tunnel_synthesize_destination.hash().toHex()); + + _jobs_running = false; + + // CBA Threading + //p thread = threading.Thread(target=Transport.jobloop, daemon=True) + //p thread.start() + + if (Reticulum::transport_enabled()) { + INFO("Transport mode is enabled"); + + // Read in path table and then write and clean in case any entries are invalid + read_path_table(); + DEBUG("Writing path table and cleaning caches to clean-up any orphaned paths/files"); + write_path_table(); + clean_caches(); + + read_tunnel_table(); + + // Create transport-specific destination for probe requests + if (Reticulum::probe_destination_enabled()) { + Destination probe_destination(_identity, Type::Destination::IN, Type::Destination::SINGLE, APP_NAME, "probe"); + probe_destination.accepts_links(false); + probe_destination.set_proof_strategy(Type::Destination::PROVE_ALL); + DEBUG("Created probe responder destination " + probe_destination.hash().toHex()); + probe_destination.announce(); + NOTICE("Transport Instance will respond to probe requests on " + probe_destination.toString()); + } + + VERBOSE("Transport instance " + _identity.toString() + " started"); + _start_time = OS::time(); + } + +// TODO +/*p + // Synthesize tunnels for any interfaces wanting it + for interface in Transport.interfaces: + interface.tunnel_id = None + if hasattr(interface, "wants_tunnel") and interface.wants_tunnel: + Transport.synthesize_tunnel(interface) +*/ + +//#ifndef NDEBUG + // CBA DEBUG + dump_stats(); +//#endif +} + +/*static*/ void Transport::loop() { + if (OS::time() > (_jobs_last_run + _job_interval)) { + jobs(); + _jobs_last_run = OS::time(); + } +} + +/*static*/ void Transport::jobs() { + //TRACE("Transport::jobs()"); + + std::vector outgoing; + std::set path_requests; + int count; + _jobs_running = true; + + try { + if (!_jobs_locked) { + + // Process active and pending link lists + if (OS::time() > (_links_last_checked + _links_check_interval)) { + std::set pending_links(_pending_links); + for (auto& link : pending_links) { + if (link.status() == Type::Link::CLOSED) { + // If we are not a Transport Instance, finding a pending link + // that was never activated will trigger an expiry of the path + // to the destination, and an attempt to rediscover the path. + if (!Reticulum::transport_enabled()) { + expire_path(link.destination().hash()); + + // If we are connected to a shared instance, it will take + // care of sending out a new path request. If not, we will + // send one directly. + if (!_owner.is_connected_to_shared_instance()) { + double last_path_request = 0; + auto iter = _path_requests.find(link.destination().hash()); + if (iter != _path_requests.end()) { + last_path_request = (*iter).second; + } + + if ((OS::time() - last_path_request) > Type::Transport::PATH_REQUEST_MI) { + DEBUG("Trying to rediscover path for " + link.destination().hash().toHex() + " since an attempted link was never established"); + //if (path_requests.find(link.destination().hash()) == path_requests.end()) { + if (path_requests.count(link.destination().hash()) == 0) { + // CBA ACCUMULATES + path_requests.insert(link.destination().hash()); + } + } + } + } + + _pending_links.erase(link); + } + } + std::set active_links(_active_links); + for (auto& link : active_links) { + if (link.status() == Type::Link::CLOSED) { + _active_links.erase(link); + } + } + + _links_last_checked = OS::time(); + } + + // Process receipts list for timed-out packets + if (OS::time() > (_receipts_last_checked + _receipts_check_interval)) { + while (_receipts.size() > Type::Transport::MAX_RECEIPTS) { + //p culled_receipt = Transport.receipts.pop(0) + PacketReceipt culled_receipt = _receipts.front(); + _receipts.pop_front(); + culled_receipt.set_timeout(-1); + culled_receipt.check_timeout(); + } + + std::list cull_receipts; + for (auto& receipt : _receipts) { + receipt.check_timeout(); + if (receipt.status() != Type::PacketReceipt::SENT) { + //p if receipt in Transport.receipts: + //p Transport.receipts.remove(receipt) + cull_receipts.push_back(receipt); + } + } + // CBA since modifying of collection while iterating is forbidden + for (auto& receipt : _receipts) { + cull_receipts.remove(receipt); + } + + _receipts_last_checked = OS::time(); + } + + // Process announces needing retransmission + if (OS::time() > (_announces_last_checked + _announces_check_interval)) { + //p for destination_hash in Transport.announce_table: + for (auto& [destination_hash, announce_entry] : _announce_table) { + //for (auto& pair : _announce_table) { + // const auto& destination_hash = pair.first; + // auto& announce_entry = pair.second; +//TRACE("[0] announce entry data size: " + std::to_string(announce_entry._packet.data().size())); + //p announce_entry = Transport.announce_table[destination_hash] + if (announce_entry._retries > 0 && announce_entry._retries >= Type::Transport::LOCAL_REBROADCASTS_MAX) { + TRACE("Completed announce processing for " + destination_hash.toHex() + ", local rebroadcast limit reached"); + // CBA OK to modify collection here since we're immediately exiting iteration + _announce_table.erase(destination_hash); + break; + } + else if (announce_entry._retries > Type::Transport::PATHFINDER_R) { + TRACE("Completed announce processing for " + destination_hash.toHex() + ", retry limit reached"); + // CBA OK to modify collection here since we're immediately exiting iteration + _announce_table.erase(destination_hash); + break; + } + else { + if (OS::time() > announce_entry._retransmit_timeout) { + TRACE("Performing announce processing for " + destination_hash.toHex() + "..."); + announce_entry._retransmit_timeout = OS::time() + Type::Transport::PATHFINDER_G + Type::Transport::PATHFINDER_RW; + announce_entry._retries += 1; + //p packet = announce_entry[5] + //p block_rebroadcasts = announce_entry[7] + //p attached_interface = announce_entry[8] + Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE; + if (announce_entry._block_rebroadcasts) { + announce_context = Type::Packet::PATH_RESPONSE; + } + //p announce_data = packet.data + Identity announce_identity(Identity::recall(announce_entry._packet.destination_hash())); + //Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown"); + //announce_destination.hash(announce_entry._packet.destination_hash()); + Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, announce_entry._packet.destination_hash()); + //P announce_destination.hexhash = announce_destination.hash.hex() + +//if (announce_entry._attached_interface) { +//TRACE("[1] interface is valid"); +//TRACE("[1] interface: " + announce_entry._attached_interface.debugString()); +//TRACE("[1] interface: " + announce_entry._attached_interface.toString()); +//} + Packet new_packet( + announce_destination, + //{Type::NONE}, + announce_entry._attached_interface, + //{Type::NONE}, + announce_entry._packet.data(), + Type::Packet::ANNOUNCE, + announce_context, + Type::Transport::TRANSPORT, + Type::Packet::HEADER_2, + Transport::_identity.hash(), + true, + announce_entry._packet.context_flag() + ); + + new_packet.hops(announce_entry._hops); + if (announce_entry._block_rebroadcasts) { + DEBUG("Rebroadcasting announce as path response for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops())); + } + else { + DEBUG("Rebroadcasting announce for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops())); + } + + outgoing.push_back(new_packet); + + // This handles an edge case where a peer sends a past + // request for a destination just after an announce for + // said destination has arrived, but before it has been + // rebroadcast locally. In such a case the actual announce + // is temporarily held, and then reinserted when the path + // request has been served to the peer. + //p if destination_hash in Transport.held_announces: + auto iter =_held_announces.find(destination_hash); + if (iter != _held_announces.end()) { + //p held_entry = Transport.held_announces.pop(destination_hash) + auto held_entry = (*iter).second; + _held_announces.erase(iter); + //p Transport.announce_table[destination_hash] = held_entry + //_announce_table[destination_hash] = held_entry; + //_announce_table.insert_or_assign({destination_hash, held_entry}); + _announce_table.erase(destination_hash); + // CBA ACCUMULATES + _announce_table.insert({destination_hash, held_entry}); + DEBUG("Reinserting held announce into table"); + // CBA Must break after erase to avoid iterator invalidation + // (same pattern as the other two erases above in this loop) + break; + } + } + } + } + + _announces_last_checked = OS::time(); + } + + // Cull the packet hashlist if it has reached its max size + if (_packet_hashlist.size() > _hashlist_maxsize) { + std::set::iterator iter = _packet_hashlist.begin(); + std::advance(iter, _packet_hashlist.size() - _hashlist_maxsize); + _packet_hashlist.erase(_packet_hashlist.begin(), iter); + } + + // Cull the path request tags list if it has reached its max size + if (_discovery_pr_tags.size() > _max_pr_tags) { + std::set::iterator iter = _discovery_pr_tags.begin(); + std::advance(iter, _discovery_pr_tags.size() - _max_pr_tags); + _discovery_pr_tags.erase(_discovery_pr_tags.begin(), iter); + } + + if (OS::time() > (_tables_last_culled + _tables_cull_interval)) { + + // CBA Disabled following since we're calling immediately after adding to path table now + // Cull the path table if it has reached its max size + //cull_path_table(); + + // Cull the reverse table according to timeout + std::vector stale_reverse_entries; + for (const auto& [packet_hash, reverse_entry] : _reverse_table) { + if (OS::time() > (reverse_entry._timestamp + REVERSE_TIMEOUT)) { + stale_reverse_entries.push_back(packet_hash); + } + } + + // Cull the link table according to timeout + std::vector stale_links; + for (const auto& [link_id, link_entry] : _link_table) { + if (link_entry._validated) { + if (OS::time() > (link_entry._timestamp + LINK_TIMEOUT)) { + stale_links.push_back(link_id); + } + } + else { + if (OS::time() > link_entry._proof_timeout) { + stale_links.push_back(link_id); + + double last_path_request = 0.0; + const auto& iter = _path_requests.find(link_entry._destination_hash); + if (iter != _path_requests.end()) { + last_path_request = (*iter).second; + } + + uint8_t lr_taken_hops = link_entry._hops; + + bool path_request_throttle = (OS::time() - last_path_request) < PATH_REQUEST_MI; + bool path_request_conditions = false; + + // If the path has been invalidated between the time of + // making the link request and now, try to rediscover it + if (!has_path(link_entry._destination_hash)) { + DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and path is now missing"); + path_request_conditions = true; + } + + // If this link request was originated from a local client + // attempt to rediscover a path to the destination, if this + // has not already happened recently. + else if (!path_request_throttle && lr_taken_hops == 0) { + DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted local client link was never established"); + path_request_conditions = true; + } + + // If the link destination was previously only 1 hop + // away, this likely means that it was local to one + // of our interfaces, and that it roamed somewhere else. + // In that case, try to discover a new path. + else if (!path_request_throttle && hops_to(link_entry._destination_hash) == 1) { + DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and destination was previously local to an interface on this instance"); + path_request_conditions = true; + } + + // If the link destination was previously only 1 hop + // away, this likely means that it was local to one + // of our interfaces, and that it roamed somewhere else. + // In that case, try to discover a new path. + else if ( !path_request_throttle and lr_taken_hops == 1) { + DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and link initiator is local to an interface on this instance"); + path_request_conditions = true; + } + + if (path_request_conditions) { + if (path_requests.count(link_entry._destination_hash) == 0) { + // CBA ACCUMULATES + path_requests.insert(link_entry._destination_hash); + } + + if (!Reticulum::transport_enabled()) { + // Drop current path if we are not a transport instance, to + // allow using higher-hop count paths or reused announces + // from newly adjacent transport instances. + expire_path(link_entry._destination_hash); + } + } + } + } + } + + // Cull the path table + std::vector stale_paths; + for (const auto& [destination_hash, destination_entry] : _destination_table) { + const Interface& attached_interface = destination_entry.receiving_interface(); + double destination_expiry; + if (attached_interface && attached_interface.mode() == Type::Interface::MODE_ACCESS_POINT) { + destination_expiry = destination_entry._timestamp + AP_PATH_TIME; + } + else if (attached_interface && attached_interface.mode() == Type::Interface::MODE_ROAMING) { + destination_expiry = destination_entry._timestamp + ROAMING_PATH_TIME; + } + else { + destination_expiry = destination_entry._timestamp + DESTINATION_TIMEOUT; + } + + if (OS::time() > destination_expiry) { + stale_paths.push_back(destination_hash); + DEBUG("Path to " + destination_hash.toHex() + " timed out and was removed"); + } + else if (_interfaces.count(attached_interface.get_hash()) == 0) { + stale_paths.push_back(destination_hash); + DEBUG("Path to " + destination_hash.toHex() + " was removed since the attached interface no longer exists"); + } + } + + // Cull the pending discovery path requests table + std::vector stale_discovery_path_requests; + for (const auto& [destination_hash, path_entry] : _discovery_path_requests) { + if (OS::time() > path_entry._timeout) { + stale_discovery_path_requests.push_back(destination_hash); + DEBUG("Waiting path request for " + destination_hash.toString() + " timed out and was removed"); + } + } + + // Cull the tunnel table + count = 0; + std::vector stale_tunnels; + for (const auto& [tunnel_id, tunnel_entry] : _tunnels) { + if (OS::time() > tunnel_entry._expires) { + stale_tunnels.push_back(tunnel_id); + TRACE("Tunnel " + tunnel_id.toHex() + " timed out and was removed"); + } + else { + std::vector stale_tunnel_paths; + for (const auto& [destination_hash, destination_entry] : tunnel_entry._serialised_paths) { + if (OS::time() > (destination_entry._timestamp + DESTINATION_TIMEOUT)) { + stale_tunnel_paths.push_back(destination_hash); + TRACE("Tunnel path to " + destination_hash.toHex() + " timed out and was removed"); + } + } + + //for (const auto& destination_hash : stale_tunnel_paths) { + for (const Bytes& destination_hash : stale_tunnel_paths) { + const_cast(tunnel_entry)._serialised_paths.erase(destination_hash); + ++count; + } + } + } + if (count > 0) { + TRACE("Removed " + std::to_string(count) + " tunnel paths"); + } + + remove_reverse_entries(stale_reverse_entries); + remove_links(stale_links); + remove_paths(stale_paths); + remove_discovery_path_requests(stale_discovery_path_requests); + remove_tunnels(stale_tunnels); + +//#ifndef NDEBUG + dump_stats(); +//#endif + + _tables_last_culled = OS::time(); + } + + // CBA Periodically persist data + //if (OS::time() > (_last_saved + _save_interval)) { + // persist_data(); + // _last_saved = OS::time(); + //} + } + else { + // Transport jobs were locked, do nothing + //p pass + } + } + catch (std::exception& e) { + ERROR("An exception occurred while running Transport jobs."); + ERRORF("The contained exception was: %s", e.what()); + } + + _jobs_running = false; + + // CBA send announce retransmission packets + for (auto& packet : outgoing) { + packet.send(); + } + + // CBA send link-related path requests + for (auto& destination_hash : path_requests) { + request_path(destination_hash); + } +} + +/*static*/ void Transport::transmit(Interface& interface, const Bytes& raw) { + TRACE("Transport::transmit()"); + // CBA + if (_callbacks._transmit_packet) { + try { + _callbacks._transmit_packet(raw, interface); + } + catch (std::exception& e) { + DEBUG("Error while executing transmit packet callback. The contained exception was: " + std::string(e.what())); + } + } + try { + //if hasattr(interface, "ifac_identity") and interface.ifac_identity != None: + if (interface.ifac_identity()) { +// TODO +/*p + // Calculate packet access code + ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:] + + // Generate mask + mask = RNS.Cryptography.hkdf( + length=len(raw)+interface.ifac_size, + derive_from=ifac, + salt=interface.ifac_key, + context=None, + ) + + // Set IFAC flag + new_header = bytes([raw[0] | 0x80, raw[1]]) + + // Assemble new payload with IFAC + new_raw = new_header+ifac+raw[2:] + + // Mask payload + i = 0; masked_raw = b"" + for byte in new_raw: + if i == 0: + // Mask first header byte, but make sure the + // IFAC flag is still set + masked_raw += bytes([byte ^ mask[i] | 0x80]) + elif i == 1 or i > interface.ifac_size+1: + // Mask second header byte and payload + masked_raw += bytes([byte ^ mask[i]]) + else: + // Don't mask the IFAC itself + masked_raw += bytes([byte]) + i += 1 + + // Send it + interface.on_outgoing(masked_raw) +*/ + } + else { + interface.send_outgoing(raw); + } + } + catch (std::exception& e) { + ERROR("Error while transmitting on " + interface.toString() + ". The contained exception was: " + e.what()); + } +} + +/*static*/ bool Transport::outbound(Packet& packet) { + TRACE("Transport::outbound()"); + ++_packets_sent; + + if (!packet.destination()) { + //throw std::invalid_argument("Can not send packet with no destination."); + ERROR("Can not send packet with no destination"); + return false; + } + + TRACE("Transport::outbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops())); + + while (_jobs_running) { + TRACE("Transport::outbound: sleeping..."); + OS::sleep(0.0005); + } + + _jobs_locked = true; + + bool sent = false; + double outbound_time = OS::time(); + + // Check if we have a known path for the destination in the path table + //if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination.type != RNS.Destination.PLAIN and packet.destination.type != RNS.Destination.GROUP and packet.destination_hash in Transport.destination_table: + if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.destination().type() != Type::Destination::PLAIN && packet.destination().type() != Type::Destination::GROUP && _destination_table.find(packet.destination_hash()) != _destination_table.end()) { + TRACE("Transport::outbound: Path to destination is known"); + //outbound_interface = Transport.destination_table[packet.destination_hash][5] + DestinationEntry destination_entry = (*_destination_table.find(packet.destination_hash())).second; + Interface outbound_interface = destination_entry.receiving_interface(); + + // If there's more than one hop to the destination, and we know + // a path, we insert the packet into transport by adding the next + // transport nodes address to the header, and modifying the flags. + // This rule applies both for "normal" transport, and when connected + // to a local shared Reticulum instance. + //if Transport.destination_table[packet.destination_hash][2] > 1: + if (destination_entry._hops > 1) { + TRACE("Forwarding packet to next closest interface..."); + if (packet.header_type() == Type::Packet::HEADER_1) { + // Insert packet into transport + //new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111) + uint8_t new_flags = (Type::Packet::HEADER_2) << 6 | (Type::Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111); + // CBA RESERVE + //Bytes new_raw; + Bytes new_raw(512); + //new_raw = struct.pack("!B", new_flags) + new_raw << new_flags; + //new_raw += packet.raw[1:2] + new_raw << packet.raw().mid(1,1); + //new_raw += Transport.destination_table[packet.destination_hash][1] + new_raw << destination_entry._received_from; + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(outbound_interface, new_raw); + //_destination_table[packet.destination_hash][0] = time.time() + destination_entry._timestamp = OS::time(); + sent = true; + } + } + + // In the special case where we are connected to a local shared + // Reticulum instance, and the destination is one hop away, we + // also add transport headers to inject the packet into transport + // via the shared instance. Normally a packet for a destination + // one hop away would just be broadcast directly, but since we + // are "behind" a shared instance, we need to get that instance + // to transport it onto the network. + //elif Transport.destination_table[packet.destination_hash][2] == 1 and Transport.owner.is_connected_to_shared_instance: + else if (destination_entry._hops == 1 && _owner.is_connected_to_shared_instance()) { + TRACE("Transport::outbound: Sending packet for directly connected interface to shared instance..."); + if (packet.header_type() == Type::Packet::HEADER_1) { + // Insert packet into transport + //new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111) + uint8_t new_flags = (Type::Packet::HEADER_2) << 6 | (Type::Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111); + // CBA RESERVE + //Bytes new_raw; + Bytes new_raw(512); + //new_raw = struct.pack("!B", new_flags) + new_raw << new_flags; + //new_raw += packet.raw[1:2] + new_raw << packet.raw().mid(1, 1); + //new_raw += Transport.destination_table[packet.destination_hash][1] + new_raw << destination_entry._received_from; + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(outbound_interface, new_raw); + //Transport.destination_table[packet.destination_hash][0] = time.time() + destination_entry._timestamp = OS::time(); + sent = true; + } + } + + // If none of the above applies, we know the destination is + // directly reachable, and also on which interface, so we + // simply transmit the packet directly on that one. + else { + TRACE("Transport::outbound: Sending packet over directly connected interface..."); + transmit(outbound_interface, packet.raw()); + sent = true; + } + } + // If we don't have a known path for the destination, we'll + // broadcast the packet on all outgoing interfaces, or the + // just the relevant interface if the packet has an attached + // interface, or belongs to a link. + else { + TRACE("Transport::outbound: Path to destination is unknown"); + bool stored_hash = false; +#if defined(INTERFACES_SET) + for (const Interface& interface : _interfaces) { +#elif defined(INTERFACES_LIST) + for (Interface& interface : _interfaces) { +#elif defined(INTERFACES_MAP) + for (auto& [hash, interface] : _interfaces) { +#endif + TRACE("Transport::outbound: Checking interface " + interface.toString()); + if (interface.OUT()) { + bool should_transmit = true; + + if (packet.destination().type() == Type::Destination::LINK) { + if (!packet.destination_link()) throw std::invalid_argument("Packet is not associated with a Link"); + if (packet.destination_link().status() == Type::Link::CLOSED) { + TRACE("Transport::outbound: Pscket destination is link-closed, not transmitting"); + should_transmit = false; + } + // CBA Bug? Destination has no member attached_interface + //z if (interface != packet.destination().attached_interface()) { + //z should_transmit = false; + //z } + } + + if (packet.attached_interface() && interface != packet.attached_interface()) { + TRACE("Transport::outbound: Packet has wrong attached interface, not transmitting"); + should_transmit = false; + } + + if (packet.packet_type() == Type::Packet::ANNOUNCE) { + if (!packet.attached_interface()) { + TRACE("Transport::outbound: Packet has no attached interface"); + if (interface.mode() == Type::Interface::MODE_ACCESS_POINT) { + TRACE("Blocking announce broadcast on " + interface.toString() + " due to AP mode"); + should_transmit = false; + } + else if (interface.mode() == Type::Interface::MODE_ROAMING) { + //local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) + //Destination local_destination({Type::NONE}); +#if defined(DESTINATIONS_SET) + bool found_local = false; + for (auto& destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + //local_destination = destination; + found_local = true; + break; + } + } + //if local_destination != None: + //if (local_destination) { + if (found_local) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(packet.destination_hash()); + //if (iter != _destinations.end()) { + // local_destination = (*iter).second; + //} + if (iter != _destinations.end()) { +#endif + TRACE("Allowing announce broadcast on roaming-mode interface from instance-local destination"); + } + else { + const Interface& from_interface = next_hop_interface(packet.destination_hash()); + //if from_interface == None or not hasattr(from_interface, "mode"): + if (!from_interface || from_interface.mode() == Type::Interface::MODE_NONE) { + should_transmit = false; + if (!from_interface) { + TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist"); + } + else if (from_interface.mode() == Type::Interface::MODE_NONE) { + TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured"); + } + } + else { + if (from_interface.mode() == Type::Interface::MODE_ROAMING) { + TRACE("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface"); + should_transmit = false; + } + else if (from_interface.mode() == Type::Interface::MODE_BOUNDARY) { + TRACE("Blocking announce broadcast on " + interface.toString() + " due to boundary-mode next-hop interface"); + should_transmit = false; + } + } + } + } + else if (interface.mode() == Type::Interface::MODE_BOUNDARY) { + //local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) + // next and filter pattern? + // next(iterable, default) + // list comprehension: [x for x in xyz if x in a] + // CBA TODO confirm that above pattern just selects the first matching destination +#if defined(DESTINATIONS_SET) + //Destination local_destination({Type::Destination::NONE}); + bool found_local = false; + for (auto& destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + //local_destination = destination; + found_local = true; + break; + } + } + //if local_destination != None: + //if (local_destination) { + if (found_local) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(packet.destination_hash()); + if (iter != _destinations.end()) { +#endif + TRACE("Allowing announce broadcast on boundary-mode interface from instance-local destination"); + } + else { + const Interface& from_interface = next_hop_interface(packet.destination_hash()); + if (!from_interface || from_interface.mode() == Type::Interface::MODE_NONE) { + should_transmit = false; + if (!from_interface) { + TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist"); + } + else if (from_interface.mode() == Type::Interface::MODE_NONE) { + TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured"); + } + } + else { + if (from_interface.mode() == Type::Interface::MODE_ROAMING) { + TRACE("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface"); + should_transmit = false; + } + } + } + } + else { + // Currently, annouces originating locally are always + // allowed, and do not conform to bandwidth caps. + // TODO: Rethink whether this is actually optimal. + if (packet.hops() > 0) { + +// TODO +/*p + if not hasattr(interface, "announce_cap"): + interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP + + if not hasattr(interface, "announce_allowed_at"): + interface.announce_allowed_at = 0 + + if not hasattr(interface, "announce_queue"): + interface.announce_queue = [] +*/ + + bool queued_announces = (interface.announce_queue().size() > 0); + if (!queued_announces && outbound_time > interface.announce_allowed_at()) { + uint16_t wait_time = 0; + if (interface.bitrate() > 0 && interface.announce_cap() > 0) { + uint16_t tx_time = (packet.raw().size() * 8) / interface.bitrate(); + wait_time = (tx_time / interface.announce_cap()); + } +#if defined(INTERFACES_SET) + const_cast(interface).announce_allowed_at(outbound_time + wait_time); +#else + interface.announce_allowed_at(outbound_time + wait_time); +#endif + } + else { + should_transmit = false; + if (interface.announce_queue().size() < Type::Reticulum::MAX_QUEUED_ANNOUNCES) { + bool should_queue = true; + for (auto& entry : interface.announce_queue()) { + if (entry._destination == packet.destination_hash()) { + uint64_t emission_timestamp = announce_emitted(packet); + should_queue = false; + if (emission_timestamp > entry._emitted) { + entry._time = outbound_time; + entry._hops = packet.hops(); + entry._emitted = emission_timestamp; + entry._raw = packet.raw(); + } + break; + } + } + if (should_queue) { + RNS::AnnounceEntry entry( + packet.destination_hash(), + outbound_time, + packet.hops(), + announce_emitted(packet), + packet.raw() + ); + + queued_announces = (interface.announce_queue().size() > 0); +#if defined(INTERFACES_SET) + const_cast(interface).add_announce(entry); +#else + // CBA ACCUMULATES + interface.add_announce(entry); +#endif + + if (!queued_announces) { + double wait_time = std::max(interface.announce_allowed_at() - OS::time(), (double)0); + + // CBA TODO THREAD? + //z timer = threading.Timer(wait_time, interface.process_announce_queue) + //z timer.start() + + if (wait_time < 1000) { + TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string((int)wait_time) + " ms"); + } + else { + TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string(OS::round(wait_time/1000,1)) + " s"); + } + } + else { + double wait_time = std::max(interface.announce_allowed_at() - OS::time(), (double)0); + if (wait_time < 1000) { + TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string((int)wait_time) + " ms"); + } + else { + TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string(OS::round(wait_time/1000,1)) + " s"); + } + } + } + } + else { + //p pass + } + } + } + else { + //p pass + } + } + } + } + + if (should_transmit) { + TRACE("Transport::outbound: Packet transmission allowed"); + if (!stored_hash) { + // CBA ACCUMULATES + _packet_hashlist.insert(packet.packet_hash()); + stored_hash = true; + } + + // TODO: Re-evaluate potential for blocking + // def send_packet(): + // Transport.transmit(interface, packet.raw) + // thread = threading.Thread(target=send_packet) + // thread.daemon = True + // thread.start() + +#if defined(INTERFACES_SET) + transmit(const_cast(interface), packet.raw()); +#else + transmit(interface, packet.raw()); +#endif + sent = true; + } + else { + TRACE("Transport::outbound: Packet transmission refused"); + } + } + } + } + + if (sent) { + packet.sent(true); + packet.sent_at(OS::time()); + + // Don't generate receipt if it has been explicitly disabled + if (packet.create_receipt() && + // Only generate receipts for DATA packets + packet.packet_type() == Type::Packet::DATA && + // Don't generate receipts for PLAIN destinations + packet.destination().type() != Type::Destination::PLAIN && + // Don't generate receipts for link-related packets + !(packet.context() >= Type::Packet::KEEPALIVE && packet.context() <= Type::Packet::LRPROOF) && + // Don't generate receipts for resource packets + !(packet.context() >= Type::Packet::RESOURCE && packet.context() <= Type::Packet::RESOURCE_RCL)) { + + PacketReceipt receipt(packet); + packet.receipt(receipt); + // CBA ACCUMULATES + _receipts.push_back(receipt); + } + + cache_packet(packet); + } + + _jobs_locked = false; + return sent; +} + +/*static*/ bool Transport::packet_filter(const Packet& packet) { + // TODO: Think long and hard about this. + // Is it even strictly necessary with the current + // transport rules? + if (packet.context() == Type::Packet::KEEPALIVE) { + return true; + } + if (packet.context() == Type::Packet::RESOURCE_REQ) { + return true; + } + if (packet.context() == Type::Packet::RESOURCE_PRF) { + return true; + } + if (packet.context() == Type::Packet::RESOURCE) { + return true; + } + if (packet.context() == Type::Packet::CACHE_REQUEST) { + return true; + } + if (packet.context() == Type::Packet::CHANNEL) { + return true; + } + + if (packet.destination_type() == Type::Destination::PLAIN) { + if (packet.packet_type() != Type::Packet::ANNOUNCE) { + if (packet.hops() > 1) { + DEBUG("Dropped PLAIN packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops"); + return false; + } + else { + return true; + } + } + else { + DEBUG("Dropped invalid PLAIN announce packet"); + return false; + } + } + + if (packet.destination_type() == Type::Destination::GROUP) { + if (packet.packet_type() != Type::Packet::ANNOUNCE) { + if (packet.hops() > 1) { + DEBUG("Dropped GROUP packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops"); + return false; + } + else { + return true; + } + } + else { + DEBUG("Dropped invalid GROUP announce packet"); + return false; + } + } + + if (_packet_hashlist.find(packet.packet_hash()) == _packet_hashlist.end()) { + TRACE("Transport::packet_filter: packet not previously seen"); + return true; + } + else { + if (packet.packet_type() == Type::Packet::ANNOUNCE) { + if (packet.destination_type() == Type::Destination::SINGLE) { + TRACE("Transport::packet_filter: packet previously seen but is SINGLE ANNOUNCE"); + return true; + } + else { + DEBUG("Dropped invalid announce packet"); + return false; + } + } + } + + DEBUG("Filtered packet with hash " + packet.packet_hash().toHex()); + return false; +} + +/*static*/ void Transport::inbound(const Bytes& raw, const Interface& interface /*= {Type::NONE}*/) { + TRACEF("Transport::inbound: received %d bytes", raw.size()); + ++_packets_received; + // CBA + if (_callbacks._receive_packet) { + try { + _callbacks._receive_packet(raw, interface); + } + catch (std::exception& e) { + DEBUG("Error while executing receive packet callback. The contained exception was: " + std::string(e.what())); + } + } +// TODO +/*p + // If interface access codes are enabled, + // we must authenticate each packet. + //if len(raw) > 2: + if (raw.size() > 2) { + if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None: + // Check that IFAC flag is set + if raw[0] & 0x80 == 0x80: + if len(raw) > 2+interface.ifac_size: + // Extract IFAC + ifac = raw[2:2+interface.ifac_size] + + // Generate mask + mask = RNS.Cryptography.hkdf( + length=len(raw), + derive_from=ifac, + salt=interface.ifac_key, + context=None, + ) + + // Unmask payload + i = 0; unmasked_raw = b"" + for byte in raw: + if i <= 1 or i > interface.ifac_size+1: + // Unmask header bytes and payload + unmasked_raw += bytes([byte ^ mask[i]]) + else: + // Don't unmask IFAC itself + unmasked_raw += bytes([byte]) + i += 1 + raw = unmasked_raw + + // Unset IFAC flag + new_header = bytes([raw[0] & 0x7f, raw[1]]) + + // Re-assemble packet + new_raw = new_header+raw[2+interface.ifac_size:] + + // Calculate expected IFAC + expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:] + + // Check it + if ifac == expected_ifac: + raw = new_raw + else: + return + + else: + return + + else: + // If the IFAC flag is not set, but should be, + // drop the packet. + return + + else: + // If the interface does not have IFAC enabled, + // check the received packet IFAC flag. + if raw[0] & 0x80 == 0x80: + // If the flag is set, drop the packet + return + } + else { + return; + } +*/ + + while (_jobs_running) { + TRACE("Transport::inbound: sleeping..."); + OS::sleep(0.0005); + } + + if (!_identity) { + WARNING("Transport::inbound: No identity!"); + return; + } + + _jobs_locked = true; + + Packet packet(RNS::Destination(RNS::Type::NONE), raw); + if (!packet.unpack()) { + WARNING("Transport::inbound: Packet unpack failed!"); + return; + } +#ifndef NDEBUG + TRACE("Transport::inbound: packet: " + packet.debugString()); +#endif + + TRACE("Transport::inbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops())); + + packet.receiving_interface(interface); + packet.hops(packet.hops() + 1); + +// TODO +/*p + if (interface) { + if hasattr(interface, "r_stat_rssi"): + if interface.r_stat_rssi != None: + packet.rssi = interface.r_stat_rssi + if len(Transport.local_client_interfaces) > 0: + Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi]) + + while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE: + Transport.local_client_rssi_cache.pop() + + if hasattr(interface, "r_stat_snr"): + if interface.r_stat_rssi != None: + packet.snr = interface.r_stat_snr + if len(Transport.local_client_interfaces) > 0: + Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr]) + + while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE: + Transport.local_client_snr_cache.pop() + } +*/ + + if (_local_client_interfaces.size() > 0) { + if (is_local_client_interface(interface)) { + packet.hops(packet.hops() - 1); + } + } + else if (interface_to_shared_instance(interface)) { + packet.hops(packet.hops() - 1); + } + + //if (packet_filter(packet)) { + // CBA + bool accept = true; + if (_callbacks._filter_packet) { + try { + accept = _callbacks._filter_packet(packet); + } + catch (std::exception& e) { + DEBUG("Error while executing filter packet callback. The contained exception was: " + std::string(e.what())); + } + } + if (accept) { + accept = packet_filter(packet); + } + if (accept) { + TRACE("Transport::inbound: Packet accepted by filter"); + + // BOUNDARY MODE: Gate backbone traffic using two whitelists. + // Whitelist 1: local device addresses (LoRa + LocalTCP) + // Whitelist 2: addresses mentioned in packets from local devices +#ifdef BOUNDARY_MODE + { + bool is_backbone = is_backbone_interface(packet.receiving_interface()); + if (is_backbone) { + bool allowed = false; + // Whitelist 1: destination is a local device + if (_boundary_local_addresses.find(packet.destination_hash()) != _boundary_local_addresses.end()) { + allowed = true; + } + // Whitelist 2: destination was mentioned by a local device + else if (_boundary_mentioned_addresses.find(packet.destination_hash()) != _boundary_mentioned_addresses.end()) { + allowed = true; + } + // Allow return traffic: proofs routed via reverse_table + // (destination is the packet hash of a packet we forwarded) + else if (_reverse_table.find(packet.destination_hash()) != _reverse_table.end()) { + allowed = true; + } + // Allow return traffic: link proofs and link data routed via link_table + // (destination is the link_id of a link we're transporting) + else if (_link_table.find(packet.destination_hash()) != _link_table.end()) { + allowed = true; + } + // Allow packets addressed to our own control destinations + // (e.g. path request handler) so backbone nodes can discover + // paths to local devices through us + else if (_control_hashes.find(packet.destination_hash()) != _control_hashes.end()) { + allowed = true; + } + // Allow packets addressed to our own registered destinations + else if (_destinations.find(packet.destination_hash()) != _destinations.end()) { + allowed = true; + } + if (!allowed) { + return; + } + } + else { + // Packet from local interface: add its destination to Whitelist 2 + _boundary_mentioned_addresses.insert(packet.destination_hash()); + } + } +#endif + + // CBA ACCUMULATES + _packet_hashlist.insert(packet.packet_hash()); + cache_packet(packet); + + // Check special conditions for local clients connected + // through a shared Reticulum instance + //p from_local_client = (packet.receiving_interface in Transport.local_client_interfaces) + bool from_local_client = (_local_client_interfaces.find(packet.receiving_interface()) != _local_client_interfaces.end()); + //p for_local_client = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.destination_table and Transport.destination_table[packet.destination_hash][2] == 0) + //p for_local_client_link = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][4] in Transport.local_client_interfaces) + //p for_local_client_link |= (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][2] in Transport.local_client_interfaces) + + // If packet is anything besides ANNOUNCE then determine if it's destinated for a local destination or link + bool for_local_client = false; + bool for_local_client_link = false; + if (packet.packet_type() != Type::Packet::ANNOUNCE) { + auto destination_iter = _destination_table.find(packet.destination_hash()); + if (destination_iter != _destination_table.end()) { + DestinationEntry destination_entry = (*destination_iter).second; + if (destination_entry._hops == 0) { + // Destined for a local destination + for_local_client = true; + } + } + auto link_iter = _link_table.find(packet.destination_hash()); + if (link_iter != _link_table.end()) { + LinkEntry link_entry = (*link_iter).second; + if (_local_client_interfaces.find(link_entry._receiving_interface) != _local_client_interfaces.end()) { + // Destined for a local link + for_local_client_link = true; + } + if (_local_client_interfaces.find(link_entry._outbound_interface) != _local_client_interfaces.end()) { + // Destined for a local link + for_local_client_link = true; + } + } + } + + // Determine if packet is proof for local destination??? + //p proof_for_local_client = (packet.destination_hash in Transport.reverse_table) and (Transport.reverse_table[packet.destination_hash][0] in Transport.local_client_interfaces) + bool proof_for_local_client = false; + auto reverse_iter = _reverse_table.find(packet.destination_hash()); + if (reverse_iter != _reverse_table.end()) { + ReverseEntry reverse_entry = (*reverse_iter).second; + if (_local_client_interfaces.find(reverse_entry._receiving_interface) != _local_client_interfaces.end()) { + // Proof for local destination??? + proof_for_local_client = true; + } + } + + // Plain broadcast packets from local clients are sent + // directly on all attached interfaces, since they are + // never injected into transport. + + // If packet is not destined for a local transport-specific destination + if (_control_hashes.find(packet.destination_hash()) == _control_hashes.end()) { + // If packet is destination type PLAIN and transport type BROADCAST + if (packet.destination_type() == Type::Destination::PLAIN && packet.transport_type() == Type::Transport::BROADCAST) { + // Send to all interfaces except the one the packet was recieved on + if (from_local_client) { +#if defined(INTERFACES_SET) + for (const Interface& interface : _interfaces) { +#elif defined(INTERFACES_LIST) + for (Interface& interface : _interfaces) { +#elif defined(INTERFACES_MAP) + for (auto& [hash, interface] : _interfaces) { +#endif + if (interface != packet.receiving_interface()) { + TRACE("Transport::inbound: Broadcasting packet on " + interface.toString()); +#if defined(INTERFACES_SET) + transmit(const_cast(interface), packet.raw()); +#else + transmit(interface, packet.raw()); +#endif + } + } + } + // If the packet was not from a local client, send + // it directly to all local clients + else { + for (const Interface& interface : _local_client_interfaces) { + TRACE("Transport::inbound: Broadcasting packet on " + interface.toString()); + transmit(const_cast(interface), packet.raw()); + } + } + } + } + + //////////////////////////////// + // TRANSPORT HANDLING + //////////////////////////////// + + // General transport handling. Takes care of directing + // packets according to transport tables and recording + // entries in reverse and link tables. + if (Reticulum::transport_enabled() || from_local_client || for_local_client || for_local_client_link) { + TRACE("Transport::inbound: Performing general transport handling"); + + // If there is no transport id, but the packet is + // for a local client, we generate the transport + // id (it was stripped on the previous hop, since + // we "spoof" the hop count for clients behind a + // shared instance, so they look directly reach- + // able), and reinsert, so the normal transport + // implementation can handle the packet. + if (!packet.transport_id() && for_local_client) { + TRACE("Transport::inbound: Regenerating transport id"); + packet.transport_id(_identity.hash()); + } + + // If this is a cache request, and we can fullfill + // it, do so and stop processing. Otherwise resume + // normal processing. + if (packet.context() == Type::Packet::CACHE_REQUEST) { + if (cache_request_packet(packet)) { + TRACE("Transport::inbound: Cached packet"); + return; + } + } + + // If the packet is in transport, check whether we + // are the designated next hop, and process it + // accordingly if we are. + if (packet.transport_id() && packet.packet_type() != Type::Packet::ANNOUNCE) { + TRACE("Transport::inbound: Packet is in transport..."); + if (packet.transport_id() == _identity.hash()) { + TRACE("Transport::inbound: We are designated next-hop"); + auto destination_iter = _destination_table.find(packet.destination_hash()); + if (destination_iter != _destination_table.end()) { + TRACE("Transport::inbound: Found next-hop path to destination"); + DestinationEntry destination_entry = (*destination_iter).second; + Bytes next_hop = destination_entry._received_from; + uint8_t remaining_hops = destination_entry._hops; + + // CBA RESERVE + //Bytes new_raw; + Bytes new_raw(512); + if (remaining_hops > 1) { + // Just increase hop count and transmit + //new_raw = packet.raw[0:1] + new_raw << packet.raw().left(1); + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += next_hop + new_raw << next_hop; + //new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:] + new_raw << packet.raw().mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2); + } + else if (remaining_hops == 1) { + // Strip transport headers and transmit + //new_flags = (RNS.Packet.HEADER_1) << 6 | (Transport.BROADCAST) << 4 | (packet.flags & 0b00001111) + uint8_t new_flags = (Type::Packet::HEADER_1) << 6 | (Type::Transport::BROADCAST) << 4 | (packet.flags() & 0b00001111); + //new_raw = struct.pack("!B", new_flags) + new_raw << new_flags; + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:] + new_raw << packet.raw().mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2); + } + else if (remaining_hops == 0) { + // Just increase hop count and transmit + //new_raw = packet.raw[0:1] + new_raw << packet.raw().left(1); + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + } + + Interface outbound_interface = destination_entry.receiving_interface(); + + if (packet.packet_type() == Type::Packet::LINKREQUEST) { + TRACE("Transport::inbound: Packet is next-hop LINKREQUEST"); + double now = OS::time(); + double proof_timeout = now + Type::Link::ESTABLISHMENT_TIMEOUT_PER_HOP * std::max((uint8_t)1, remaining_hops); + LinkEntry link_entry( + now, + next_hop, + outbound_interface, + remaining_hops, + packet.receiving_interface(), + packet.hops(), + packet.destination_hash(), + false, + proof_timeout + ); + // CBA ACCUMULATES + _link_table.insert({Link::link_id_from_lr_packet(packet), link_entry}); + } + else { + TRACE("Transport::inbound: Packet is next-hop other type"); + ReverseEntry reverse_entry( + packet.receiving_interface(), + outbound_interface, + OS::time() + ); + // CBA ACCUMULATES + _reverse_table.insert({packet.getTruncatedHash(), reverse_entry}); + } + TRACE("Transport::outbound: Sending packet to next hop..."); +#if defined(INTERFACES_SET) + transmit(const_cast(outbound_interface), new_raw); +#else + transmit(outbound_interface, new_raw); +#endif + destination_entry._timestamp = OS::time(); + } + else { +#ifdef BOUNDARY_MODE + // BOUNDARY MODE: No path to destination. If packet came from + // a local device (non-backbone), request the path. + { + bool from_backbone = is_backbone_interface(packet.receiving_interface()); + if (!from_backbone) { + DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local device packet. Requesting path."); + request_path(packet.destination_hash()); + } + } +#else + // TODO: There should probably be some kind of REJECT + // mechanism here, to signal to the source that their + // expected path failed. + TRACE("Got packet in transport, but no known path to final destination " + packet.destination_hash().toHex() + ". Dropping packet."); +#endif + } + } + else { + TRACE("Transport::inbound: We are not designated next-hop so not transporting"); + } + } + else { + TRACE("Transport::inbound: Either packet is announce or packet has no next-hop (possibly for a local destination)"); +#ifdef BOUNDARY_MODE + // BOUNDARY MODE: If this packet came from a local interface and we + // have a path to the destination, wrap it with transport headers + // and forward it through the backbone as the first transport hop. + // Skip ANNOUNCE and PROOF packets — announces have their own handling, + // and link proofs (LRPROOF) are handled by the LRPROOF transport code. + // Also skip packets destined for locally-registered destinations + // (e.g. path request handler) — those must be processed locally. + bool is_local_destination = false; +#if defined(DESTINATIONS_MAP) + is_local_destination = (_destinations.find(packet.destination_hash()) != _destinations.end()); +#elif defined(DESTINATIONS_SET) + for (auto& dest : _destinations) { + if (dest.hash() == packet.destination_hash()) { is_local_destination = true; break; } + } +#endif + if (!is_local_destination && packet.packet_type() != Type::Packet::ANNOUNCE && packet.packet_type() != Type::Packet::PROOF) { + bool is_from_backbone = is_backbone_interface(packet.receiving_interface()); + if (!is_from_backbone) { + auto destination_iter = _destination_table.find(packet.destination_hash()); + if (destination_iter != _destination_table.end()) { + DestinationEntry& dest_entry = (*destination_iter).second; + Bytes next_hop = dest_entry._received_from; + uint8_t remaining_hops = dest_entry._hops; + Interface outbound_interface = dest_entry.receiving_interface(); + + // Build outgoing packet based on remaining hops, + // mirroring standard transport forwarding logic. + Bytes new_raw(512); + if (remaining_hops > 1) { + // Multi-hop: wrap with HEADER_2/TRANSPORT, + // setting transport_id = next_hop (the next + // transport node's identity hash from announce). + uint8_t new_flags = (Type::Packet::HEADER_2) << 6 + | (Type::Transport::TRANSPORT) << 4 + | (packet.flags() & 0b00001111); + new_raw << new_flags; + new_raw << packet.hops(); + new_raw << next_hop; // insert transport_id + new_raw << packet.raw().mid(2); // destination_hash + payload + } + else { + // Single hop (remaining_hops <= 1): destination is + // directly reachable. Send as HEADER_1/BROADCAST + // (no transport header), matching standard transport + // behaviour for final-hop delivery. + uint8_t new_flags = (Type::Packet::HEADER_1) << 6 + | (Type::Transport::BROADCAST) << 4 + | (packet.flags() & 0b00001111); + new_raw << new_flags; + new_raw << packet.hops(); + new_raw << packet.raw().mid(2); // destination_hash + payload + } + + // Create link_table or reverse_table entry for return path + if (packet.packet_type() == Type::Packet::LINKREQUEST) { + double now = OS::time(); + double proof_timeout = now + Type::Link::ESTABLISHMENT_TIMEOUT_PER_HOP + * std::max((uint8_t)1, remaining_hops); + LinkEntry link_entry( + now, next_hop, outbound_interface, remaining_hops, + packet.receiving_interface(), packet.hops(), + packet.destination_hash(), false, proof_timeout + ); + // Each LINKREQUEST gets its own entry (unique link_id) + _link_table.insert({Link::link_id_from_lr_packet(packet), link_entry}); + } + else { + ReverseEntry reverse_entry( + packet.receiving_interface(), outbound_interface, OS::time() + ); + _reverse_table.insert({packet.getTruncatedHash(), reverse_entry}); + } + + DEBUG("BOUNDARY: Forwarding local packet (" + std::to_string(remaining_hops) + " hops, " + std::to_string(new_raw.size()) + " bytes) to " + outbound_interface.toString() + " for " + packet.destination_hash().toHex()); + transmit(outbound_interface, new_raw); + dest_entry._timestamp = OS::time(); + } + else { + DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local packet. Requesting path."); + request_path(packet.destination_hash()); + } + } + else { + // BOUNDARY MODE REVERSE: Packet came from backbone, + // check if destination is a local LoRa device and forward it. + if (_boundary_local_addresses.find(packet.destination_hash()) != _boundary_local_addresses.end()) { + auto destination_iter = _destination_table.find(packet.destination_hash()); + if (destination_iter != _destination_table.end()) { + DestinationEntry& dest_entry = (*destination_iter).second; + Interface outbound_interface = dest_entry.receiving_interface(); + + // Create reverse_table entry so proof can get back + ReverseEntry reverse_entry( + packet.receiving_interface(), outbound_interface, OS::time() + ); + _reverse_table.insert({packet.getTruncatedHash(), reverse_entry}); + + DEBUG("BOUNDARY: Forwarding backbone packet to local device for " + packet.destination_hash().toHex() + " via " + outbound_interface.toString()); + transmit(outbound_interface, packet.raw()); + dest_entry._timestamp = OS::time(); + } + } + } + } +#endif + } + + // Link transport handling. Directs packets according + // to entries in the link tables + if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.packet_type() != Type::Packet::LINKREQUEST && packet.context() != Type::Packet::LRPROOF) { + TRACE("Transport::inbound: Checking if packet is meant for link transport..."); + auto link_iter = _link_table.find(packet.destination_hash()); + if (link_iter != _link_table.end()) { + TRACE("Transport::inbound: Found link entry, handling link transport"); + LinkEntry link_entry = (*link_iter).second; + // If receiving and outbound interface is + // the same for this link, direction doesn't + // matter, and we simply send the packet on. + Interface outbound_interface({Type::NONE}); + if (link_entry._outbound_interface == link_entry._receiving_interface) { + // But check that taken hops matches one + // of the expectede values. + if (packet.hops() == link_entry._remaining_hops || packet.hops() == link_entry._hops) { + TRACE("Transport::inbound: Link inbound/outbound interfaes are same, transporting on same interface"); + outbound_interface = link_entry._outbound_interface; + } + } + else { + // If interfaces differ, we transmit on + // the opposite interface of what the + // packet was received on. + if (packet.receiving_interface() == link_entry._outbound_interface) { + // Also check that expected hop count matches + if (packet.hops() == link_entry._remaining_hops) { + TRACE("Transport::inbound: Link transporting on inbound interface"); + outbound_interface = link_entry._receiving_interface; + } + } + else if (packet.receiving_interface() == link_entry._receiving_interface) { + // Also check that expected hop count matches + if (packet.hops() == link_entry._hops) { + TRACE("Transport::inbound: Link transporting on outbound interface"); + outbound_interface = link_entry._outbound_interface; + } + } + } + + if (outbound_interface) { + TRACE("Transport::inbound: Transmitting link transport packet"); + // CBA RESERVE + //Bytes new_raw; + Bytes new_raw(512); + //new_raw = packet.raw[0:1] + new_raw << packet.raw().left(1); + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(outbound_interface, new_raw); + link_entry._timestamp = OS::time(); + } + else { + //p pass + } + } + } + } + + //////////////////////////////// + // LOCAL HANDLING + //////////////////////////////// + + // Announce handling. Handles logic related to incoming + // announces, queueing rebroadcasts of these, and removal + // of queued announce rebroadcasts once handed to the next node. + if (packet.packet_type() == Type::Packet::ANNOUNCE) { + TRACE("Transport::inbound: Packet is ANNOUNCE"); + Bytes received_from; + //p local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) +#if defined(DESTINATIONS_SET) + //Destination local_destination({Type::NONE}); + bool found_local = false; + for (auto& destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + //local_destination = destination; + found_local = true; + break; + } + } + //if local_destination == None and RNS.Identity.validate_announce(packet): + //if (!local_destination && Identity::validate_announce(packet)) { + if (!found_local && Identity::validate_announce(packet)) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(packet.destination_hash()); + if (iter == _destinations.end() && Identity::validate_announce(packet)) { +#endif + TRACE("Transport::inbound: Packet is announce for non-local destination, processing..."); + if (packet.transport_id()) { + received_from = packet.transport_id(); + + // Check if this is a next retransmission from + // another node. If it is, we're removing the + // announce in question from our pending table + if (Reticulum::transport_enabled() && _announce_table.count(packet.destination_hash()) > 0) { + //AnnounceEntry& announce_entry = _announce_table[packet.destination_hash()]; + AnnounceEntry& announce_entry = (*_announce_table.find(packet.destination_hash())).second; + + if ((packet.hops() - 1) == announce_entry._hops) { + DEBUG("Heard a local rebroadcast of announce for " + packet.destination_hash().toHex()); + announce_entry._local_rebroadcasts += 1; + if (announce_entry._local_rebroadcasts >= LOCAL_REBROADCASTS_MAX) { + DEBUG("Max local rebroadcasts of announce for " + packet.destination_hash().toHex() + " reached, dropping announce from our table"); + _announce_table.erase(packet.destination_hash()); + } + } + + if ((packet.hops() - 1) == (announce_entry._hops + 1) && announce_entry._retries > 0) { + double now = OS::time(); + if (now < announce_entry._timestamp) { + DEBUG("Rebroadcasted announce for " + packet.destination_hash().toHex() + " has been passed on to another node, no further tries needed"); + _announce_table.erase(packet.destination_hash()); + } + } + } + } + else { + received_from = packet.destination_hash(); + } + + // Check if this announce should be inserted into + // announce and destination tables + bool should_add = false; + + // First, check that the announce is not for a destination + // local to this system, and that hops are less than the max + // CBA TODO determine why packet destination hash is being searched in destinations again since we entered this logic becuase it did not exist above + //if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1): +#if defined(DESTINATIONS_SET) + bool found_local = false; + for (auto& destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + found_local = true; + break; + } + } + if (!found_local && packet.hops() < (PATHFINDER_M+1)) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(packet.destination_hash()); + if (iter == _destinations.end() && packet.hops() < (PATHFINDER_M+1)) { +#endif + uint64_t announce_emitted = Transport::announce_emitted(packet); + + //p random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10] + Bytes random_blob = packet.data().mid(Type::Identity::KEYSIZE/8 + Type::Identity::NAME_HASH_LENGTH/8, Type::Identity::RANDOM_HASH_LENGTH/8); + //p random_blobs = [] + std::set empty_random_blobs; + std::set& random_blobs = empty_random_blobs; + auto iter = _destination_table.find(packet.destination_hash()); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + //p random_blobs = Transport.destination_table[packet.destination_hash][4] + random_blobs = destination_entry._random_blobs; + + // If we already have a path to the announced + // destination, but the hop count is equal or + // less, we'll update our tables. + if (packet.hops() <= destination_entry._hops) { + // Make sure we haven't heard the random + // blob before, so announces can't be + // replayed to forge paths. + // TODO: Check whether this approach works + // under all circumstances + //p if not random_blob in random_blobs: + if (random_blobs.find(random_blob) == random_blobs.end()) { + should_add = true; + } + else { + should_add = false; + } + } + else { + // If an announce arrives with a larger hop + // count than we already have in the table, + // ignore it, unless the path is expired, or + // the emission timestamp is more recent. + double now = OS::time(); + double path_expires = destination_entry._expires; + + uint64_t path_announce_emitted = 0; + for (const Bytes& path_random_blob : random_blobs) { + //p path_announce_emitted = max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big")) + path_announce_emitted = std::max(path_announce_emitted, OS::from_bytes_big_endian(path_random_blob.data() + 5, 5)); + if (path_announce_emitted >= announce_emitted) { + break; + } + } + + if (now >= path_expires) { + // We also check that the announce is + // different from ones we've already heard, + // to avoid loops in the network + if (random_blobs.find(random_blob) == random_blobs.end()) { + // TODO: Check that this ^ approach actually + // works under all circumstances + DEBUG("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce due to expired path"); + should_add = true; + } + else { + should_add = false; + } + } + else { + if (announce_emitted > path_announce_emitted) { + if (random_blobs.find(random_blob) == random_blobs.end()) { + DEBUG("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce, since it was more recently emitted"); + should_add = true; + } + else { + should_add = false; + } + } + } + } + } + else { + // If this destination is unknown in our table + // we should add it + should_add = true; + } + + if (should_add) { + // BOUNDARY MODE: Prevent backbone echo from overwriting a local LoRa path +#ifdef BOUNDARY_MODE + { + auto existing_iter = _destination_table.find(packet.destination_hash()); + if (existing_iter != _destination_table.end()) { + DestinationEntry& existing = existing_iter->second; + Interface existing_iface = existing.receiving_interface(); + bool existing_is_lora = !is_backbone_interface(existing_iface); + bool new_is_backbone = is_backbone_interface(packet.receiving_interface()); + if (existing_is_lora && new_is_backbone) { + DEBUG("BOUNDARY: Blocking backbone announce echo from overwriting local LoRa path for " + packet.destination_hash().toHex() + + " (existing " + std::to_string(existing._hops) + " hops via LoRa, new " + std::to_string(packet.hops()) + " hops via backbone)"); + should_add = false; + } + } + } +#endif + } + + if (should_add) { + double now = OS::time(); + + bool rate_blocked = false; + +// TODO +/*p + if packet.context != RNS.Packet.PATH_RESPONSE and packet.receiving_interface.announce_rate_target != None: + if not packet.destination_hash in Transport.announce_rate_table: + rate_entry = { "last": now, "rate_violations": 0, "blocked_until": 0, "timestamps": [now]} + Transport.announce_rate_table[packet.destination_hash] = rate_entry + + else: + rate_entry = Transport.announce_rate_table[packet.destination_hash] + rate_entry["timestamps"].append(now) + + while len(rate_entry["timestamps"]) > Transport.MAX_RATE_TIMESTAMPS: + rate_entry["timestamps"].pop(0) + + current_rate = now - rate_entry["last"] + + if now > rate_entry["blocked_until"]: + + if current_rate < packet.receiving_interface.announce_rate_target: + rate_entry["rate_violations"] += 1 + + else: + rate_entry["rate_violations"] = std::max(0, rate_entry["rate_violations"]-1) + + if rate_entry["rate_violations"] > packet.receiving_interface.announce_rate_grace: + rate_target = packet.receiving_interface.announce_rate_target + rate_penalty = packet.receiving_interface.announce_rate_penalty + rate_entry["blocked_until"] = rate_entry["last"] + rate_target + rate_penalty + rate_blocked = True + else: + rate_entry["last"] = now + + else: + rate_blocked = True +*/ + + uint8_t retries = 0; + uint8_t announce_hops = packet.hops(); + uint8_t local_rebroadcasts = 0; + bool block_rebroadcasts = false; + Interface attached_interface = {Type::NONE}; + + double retransmit_timeout = now + (Cryptography::random() * PATHFINDER_RW); + + double expires; + if (packet.receiving_interface().mode() == Type::Interface::MODE_ACCESS_POINT) { + expires = now + AP_PATH_TIME; + } + else if (packet.receiving_interface().mode() == Type::Interface::MODE_ROAMING) { + expires = now + ROAMING_PATH_TIME; + } + else { + expires = now + PATHFINDER_E; + } + + random_blobs.insert(random_blob); + + if ((Reticulum::transport_enabled() || Transport::from_local_client(packet)) && packet.context() != Type::Packet::PATH_RESPONSE) { + // Insert announce into announce table for retransmission + + if (rate_blocked) { + DEBUG("Blocking rebroadcast of announce from " + packet.destination_hash().toHex() + " due to excessive announce rate"); + } + else { + if (Transport::from_local_client(packet)) { + // If the announce is from a local client, + // it is announced immediately, but only one time. + retransmit_timeout = now; + retries = PATHFINDER_R; + } + AnnounceEntry announce_entry( + now, + retransmit_timeout, + retries, + received_from, + announce_hops, + packet, + local_rebroadcasts, + block_rebroadcasts, + attached_interface + ); + // CBA ACCUMULATES + _announce_table.insert({packet.destination_hash(), announce_entry}); + } + } + // TODO: Check from_local_client once and store result + else if (Transport::from_local_client(packet) && packet.context() == Type::Packet::PATH_RESPONSE) { + // If this is a path response from a local client, + // check if any external interfaces have pending + // path requests. + //p if packet.destination_hash in Transport.pending_local_path_requests: + auto iter = _pending_local_path_requests.find(packet.destination_hash()); + if (iter != _pending_local_path_requests.end()) { + //p desiring_interface = Transport.pending_local_path_requests.pop(packet.destination_hash) + //const Interface& desiring_interface = (*iter).second; + retransmit_timeout = now; + retries = PATHFINDER_R; + + AnnounceEntry announce_entry( + now, + retransmit_timeout, + retries, + received_from, + announce_hops, + packet, + local_rebroadcasts, + block_rebroadcasts, + attached_interface + ); + // CBA ACCUMULATES + _announce_table.insert({packet.destination_hash(), announce_entry}); + } + } + + // If we have any local clients connected, we re- + // transmit the announce to them immediately + if (_local_client_interfaces.size() > 0) { + Identity announce_identity(Identity::recall(packet.destination_hash())); + //Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown"); + //announce_destination.hash(packet.destination_hash()); + Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, packet.destination_hash()); + //announce_destination.hexhash(announce_destination.hash().toHex()); + Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE; + Bytes announce_data = packet.data(); + + // TODO: Shouldn't the context be PATH_RESPONSE in the first case here? + if (Transport::from_local_client(packet) && packet.context() == Type::Packet::PATH_RESPONSE) { + for (const Interface& local_interface : _local_client_interfaces) { + if (packet.receiving_interface() != local_interface) { + Packet new_announce( + announce_destination, + local_interface, + announce_data, + Type::Packet::ANNOUNCE, + announce_context, + Type::Transport::TRANSPORT, + Type::Packet::HEADER_2, + _identity.hash(), + true, + packet.context_flag() + ); + + new_announce.hops(packet.hops()); + new_announce.send(); + } + } + } + else { + for (const Interface& local_interface : _local_client_interfaces) { + if (packet.receiving_interface() != local_interface) { + Packet new_announce( + announce_destination, + local_interface, + announce_data, + Type::Packet::ANNOUNCE, + announce_context, + Type::Transport::TRANSPORT, + Type::Packet::HEADER_2, + _identity.hash(), + true, + packet.context_flag() + ); + + new_announce.hops(packet.hops()); + new_announce.send(); + } + } + } + } + + // If we have any waiting discovery path requests + // for this destination, we retransmit to that + // interface immediately + auto iter = _discovery_path_requests.find(packet.destination_hash()); + if (iter != _discovery_path_requests.end()) { + PathRequestEntry& pr_entry = (*iter).second; + attached_interface = pr_entry._requesting_interface; + + DEBUG("Got matching announce, answering waiting discovery path request for " + packet.destination_hash().toHex() + " on " + attached_interface.toString()); + Identity announce_identity(Identity::recall(packet.destination_hash())); + //Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown"); + //announce_destination.hash(packet.destination_hash()); + Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, packet.destination_hash()); + //announce_destination.hexhash(announce_destination.hash().toHex()); + Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE; + Bytes announce_data = packet.data(); + + Packet new_announce( + announce_destination, + attached_interface, + announce_data, + Type::Packet::ANNOUNCE, + Type::Packet::PATH_RESPONSE, + Type::Transport::TRANSPORT, + Type::Packet::HEADER_2, + _identity.hash(), + true, + packet.context_flag() + ); + + new_announce.hops(packet.hops()); + new_announce.send(); + } + + // CBA Culling before adding to esnure table does not exceed maxsize + TRACE("Caching packet " + packet.get_hash().toHex()); + if (RNS::Transport::cache_packet(packet, true)) { + packet.cached(true); + } + //TRACE("Adding packet " + packet.get_hash().toHex() + " to packet table"); + //PacketEntry packet_entry(packet); + // CBA ACCUMULATES + //_packet_table.insert({packet.get_hash(), packet_entry}); + TRACE("Adding destination " + packet.destination_hash().toHex() + " to path table"); + DestinationEntry destination_table_entry( + now, + received_from, + announce_hops, + expires, + random_blobs, + //packet.receiving_interface(), + //const_cast(packet.receiving_interface()), + packet.receiving_interface().get_hash(), + //packet + packet.get_hash() + ); + // CBA ACCUMULATES + if (_destination_table.insert({packet.destination_hash(), destination_table_entry}).second) { + ++_destinations_added; + cull_path_table(); + } + + DEBUG("Destination " + packet.destination_hash().toHex() + " is now " + std::to_string(announce_hops) + " hops away via " + received_from.toHex() + " on " + packet.receiving_interface().toString()); + + // BOUNDARY MODE: Register destinations seen via non-backbone interfaces (Whitelist 1) +#ifdef BOUNDARY_MODE + { + bool is_backbone = is_backbone_interface(packet.receiving_interface()); + if (!is_backbone) { + _boundary_local_addresses.insert(packet.destination_hash()); + DEBUG("BOUNDARY: Registered local address " + packet.destination_hash().toHex() + " from local interface"); + } + } +#endif + //TRACE("Transport::inbound: Destination " + packet.destination_hash().toHex() + " has data: " + packet.data().toHex()); + //TRACE("Transport::inbound: Destination " + packet.destination_hash().toHex() + " has text: " + packet.data().toString()); + +// TODO +/* + // If the receiving interface is a tunnel, we add the + // announce to the tunnels table + if (packet.receiving_interface().tunnel_id()) { + tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]; + paths = tunnel_entry[2]; + paths[packet.destination_hash] = destination_table_entry; + expires = OS::time() + Transport::DESTINATION_TIMEOUT; + tunnel_entry[3] = expires; + DEBUG("Path to " + packet.destination_hash().toHex() + " associated with tunnel " + packet.receiving_interface().tunnel_id().toHex()); + } +*/ + + // Call externally registered callbacks from apps + // wanting to know when an announce arrives + if (packet.context() != Type::Packet::PATH_RESPONSE) { + TRACE("Transport::inbound: Not path response, sending to announce handler..."); + for (auto& handler : _announce_handlers) { + TRACE("Transport::inbound: Checking filter of announce handler..."); + try { + // Check that the announced destination matches + // the handlers aspect filter + bool execute_callback = false; + Identity announce_identity(Identity::recall(packet.destination_hash())); + if (handler->aspect_filter().empty()) { + // If the handlers aspect filter is set to + // None, we execute the callback in all cases + execute_callback = true; + } + else { + Bytes handler_expected_hash = Destination::hash_from_name_and_identity(handler->aspect_filter().c_str(), announce_identity); + if (packet.destination_hash() == handler_expected_hash) { + execute_callback = true; + } + } + if (execute_callback) { + // CBA TODO Why does app data come from recall instead of from this announce packet? + handler->received_announce( + packet.destination_hash(), + announce_identity, + Identity::recall_app_data(packet.destination_hash()) + ); + } + } + catch (std::exception& e) { + ERROR("Error while processing external announce callback."); + ERRORF("The contained exception was: %s", e.what()); + } + } + } + } + } + else { + TRACE("Transport::inbound: Packet is announce for local destination, not processing"); + } + } + else { + TRACE("Transport::inbound: Packet is announce for local destination, not processing"); + } + } + + // Handling for link requests to local destinations + else if (packet.packet_type() == Type::Packet::LINKREQUEST) { + TRACE("Transport::inbound: Packet is LINKREQUEST"); + if (!packet.transport_id() || packet.transport_id() == _identity.hash()) { + TRACE("Transport::inbound: Checking if LINKREQUEST is for local destination"); +#if defined(DESTINATIONS_SET) + for (auto& destination : _destinations) { + if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(packet.destination_hash()); + if (iter != _destinations.end()) { + auto& destination = (*iter).second; + if (destination.type() == packet.destination_type()) { +#endif + TRACE("Transport::inbound: Found local destination for LINKREQUEST"); + packet.destination(destination); + // CBA iterator over std::set is always const so need to make temporarily mutable + //destination.receive(packet); +#if defined(DESTINATIONS_SET) + const_cast(destination).receive(packet); +#else + destination.receive(packet); +#endif + } + } + } + } + + // Handling for data packets to local destinations + else if (packet.packet_type() == Type::Packet::DATA) { + TRACE("Transport::inbound: Packet is DATA"); + if (packet.destination_type() == Type::Destination::LINK) { + // Data is destined for a link + TRACE("Transport::inbound: Packet is DATA for a LINK"); + std::set active_links(_active_links); + for (auto& link : active_links) { + if (link.link_id() == packet.destination_hash()) { + TRACE("Transport::inbound: Packet is DATA for an active LINK"); + packet.link(link); + const_cast(link).receive(packet); + } + } + } + else { + // Data is basic (not destined for a link) +#if defined(DESTINATIONS_SET) + for (auto& destination : _destinations) { + if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(packet.destination_hash()); + if (iter != _destinations.end()) { + // Data is for a local destination + DEBUG("Packet destination " + packet.destination_hash().toHex() + " found, destination is local"); + auto& destination = (*iter).second; + if (destination.type() == packet.destination_type()) { + TRACE("Transport::inbound: Packet destination type " + std::to_string(packet.destination_type()) + " matched, processing"); +#endif + packet.destination(destination); +#if defined(DESTINATIONS_SET) + const_cast(destination).receive(packet); +#else + destination.receive(packet); +#endif + + if (destination.proof_strategy() == Type::Destination::PROVE_ALL) { + packet.prove(); + } + else if (destination.proof_strategy() == Type::Destination::PROVE_APP) { + if (destination.callbacks()._proof_requested) { + try { + if (destination.callbacks()._proof_requested(packet)) { + packet.prove(); + } + } + catch (std::exception& e) { + ERROR(std::string("Error while executing proof request callback. The contained exception was: ") + e.what()); + } + } + } + } + else { + DEBUG("Transport::inbound: Packet destination type " + std::to_string(packet.destination_type()) + " mismatch, ignoring"); + } + } + else { + DEBUG("Transport::inbound: Local destination " + packet.destination_hash().toHex() + " not found, not handling packet locally"); + } + } + } + + // Handling for proofs and link-request proofs + else if (packet.packet_type() == Type::Packet::PROOF) { + TRACE("Transport::inbound: Packet is PROOF"); + if (packet.context() == Type::Packet::LRPROOF) { + TRACE("Transport::inbound: Packet is LINK PROOF"); + // This is a link request proof, check if it + // needs to be transported + if ((Reticulum::transport_enabled() || for_local_client_link || from_local_client) && _link_table.find(packet.destination_hash()) != _link_table.end()) { + TRACE("Handling link request proof..."); + LinkEntry link_entry = (*_link_table.find(packet.destination_hash())).second; + DEBUG("PROOF DEBUG: recv_iface=" + packet.receiving_interface().toString() + " out_iface=" + link_entry._outbound_interface.toString()); + + bool interface_match = (packet.receiving_interface() == link_entry._outbound_interface); + if (interface_match) { + try { + size_t expected_size = (Type::Identity::SIGLENGTH/8 + Type::Link::ECPUBSIZE/2); + size_t expected_size_with_mtu = expected_size + Type::Link::LINK_MTU_SIZE; + DEBUG("PROOF DEBUG: data_size=" + std::to_string(packet.data().size()) + " expected=" + std::to_string(expected_size) + " or " + std::to_string(expected_size_with_mtu) + " raw_size=" + std::to_string(packet.raw().size())); + if (packet.data().size() == expected_size || packet.data().size() == expected_size_with_mtu) { + Bytes signalling_bytes; + if (packet.data().size() == expected_size_with_mtu) { + signalling_bytes = Link::signalling_bytes(Link::mtu_from_lp_packet(packet), Link::mode_from_lp_packet(packet)); + } + + Bytes peer_pub_bytes = packet.data().mid(Type::Identity::SIGLENGTH/8, Type::Link::ECPUBSIZE/2); + Identity peer_identity = Identity::recall(link_entry._destination_hash); + if (!peer_identity) { + DEBUG("PROOF DEBUG: Cannot recall identity for " + link_entry._destination_hash.toHex() + ", dropping proof."); + } + else { + DEBUG("PROOF DEBUG: peer_identity recalled for " + link_entry._destination_hash.toHex() + " valid=" + std::to_string(!peer_identity.get_public_key().empty())); + Bytes peer_sig_pub_bytes = peer_identity.get_public_key().mid(Type::Link::ECPUBSIZE/2, Type::Link::ECPUBSIZE/2); + + Bytes signed_data = packet.destination_hash() + peer_pub_bytes + peer_sig_pub_bytes + signalling_bytes; + Bytes signature = packet.data().left(Type::Identity::SIGLENGTH/8); + + if (peer_identity.validate(signature, signed_data)) { + TRACE("Link request proof validated for transport via " + link_entry._receiving_interface.toString()); + //p new_raw = packet.raw[0:1] + // CBA RESERVE + //Bytes new_raw = packet.raw().left(1); + Bytes new_raw(512); + new_raw << packet.raw().left(1); + //p new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //p new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + link_entry._validated = true; + transmit(link_entry._receiving_interface, new_raw); + } + else { + DEBUG("Invalid link request proof in transport for link " + packet.destination_hash().toHex() + ", dropping proof."); + } + } // end peer_identity valid + } + } + catch (std::exception& e) { + ERROR("Error while transporting link request proof. The contained exception was: " + std::string(e.what())); + } + } + else { + DEBUG("PROOF DEBUG: interface mismatch - recv=" + packet.receiving_interface().toString() + " expected_out=" + link_entry._outbound_interface.toString()); + DEBUG("Link request proof received on wrong interface, not transporting it."); + } + } + else { + // Check if we can deliver it to a local + // pending link + TRACEF("Handling proof for link request %s", packet.destination_hash().toHex().c_str()); + // CBA Must make a copy of _pending_links before traversing since it gets modified + //for (auto link : _pending_links) { + std::set pending_links(_pending_links); + for (auto& link : pending_links) { + TRACEF("Checking for link request handling by pending link %s", link.link_id().toHex().c_str()); + if (link.link_id() == packet.destination_hash()) { + TRACE("Requesting pending link to validate proof"); + const_cast(link).validate_proof(packet); + } + } + } + } + else if (packet.context() == Type::Packet::RESOURCE_PRF) { + TRACE("Transport::inbound: Packet is RESOURCE PROOF"); + std::set active_links(_active_links); + for (auto& link : active_links) { + if (link.link_id() == packet.destination_hash()) { + const_cast(link).receive(packet); + } + } + } + else { + TRACE("Transport::inbound: Packet is regular PROOF"); + if (packet.destination_type() == Type::Destination::LINK) { + std::set active_links(_active_links); + for (auto& link : active_links) { + if (link.link_id() == packet.destination_hash()) { + packet.link(link); + } + } + } + + Bytes proof_hash; + if (packet.data().size() == Type::PacketReceipt::EXPL_LENGTH) { + proof_hash = packet.data().left(Type::Identity::HASHLENGTH/8); + } + + // Check if this proof needs to be transported + if ((Reticulum::transport_enabled() || from_local_client || proof_for_local_client) && _reverse_table.find(packet.destination_hash()) != _reverse_table.end()) { + ReverseEntry reverse_entry = (*_reverse_table.find(packet.destination_hash())).second; + if (packet.receiving_interface() == reverse_entry._outbound_interface) { + TRACE("Proof received on correct interface, transporting it via " + reverse_entry._receiving_interface.toString()); + //p new_raw = packet.raw[0:1] + // CBA RESERVE + //Bytes new_raw = packet.raw().left(1); + Bytes new_raw(512); + new_raw << packet.raw().left(1); + //p new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //p new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(reverse_entry._receiving_interface, new_raw); + } + else { + DEBUG("Proof received on wrong interface, not transporting it."); + } + } + else { + TRACE("Proof is not candidate for transporting"); + } + + std::list cull_receipts; + for (auto& receipt : _receipts) { + bool receipt_validated = false; + if (proof_hash) { + // Only test validation if hash matches + if (receipt.hash() == proof_hash) { + receipt_validated = receipt.validate_proof_packet(packet); + } + } + else { + // TODO: This looks like it should actually + // be rewritten when implicit proofs are added. + + // In case of an implicit proof, we have + // to check every single outstanding receipt + receipt_validated = receipt.validate_proof_packet(packet); + } + + // CBA TODO requires modifying of collection while iterating which is forbidden + if (receipt_validated) { + //p if receipt in Transport.receipts: + //p Transport.receipts.remove(receipt) + cull_receipts.push_back(receipt); + } + } + // CBA since modifying of collection while iterating is forbidden + for (auto& receipt : _receipts) { + cull_receipts.remove(receipt); + } + } + } + } + + _jobs_locked = false; +} + +/*static*/ void Transport::synthesize_tunnel(const Interface& interface) { +// TODO +/*p + Bytes interface_hash = interface.get_hash(); + Bytes public_key = _identity.get_public_key(); + Bytes random_hash = Identity::get_random_hash(); + + tunnel_id_data = public_key+interface_hash + tunnel_id = RNS.Identity.full_hash(tunnel_id_data) + + signed_data = tunnel_id_data+random_hash + signature = Transport.identity.sign(signed_data) + + data = signed_data+signature + + tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize") + + packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface) + packet.send() + + interface.wants_tunnel = False +*/ +} + +/*static*/ void Transport::tunnel_synthesize_handler(const Bytes& data, const Packet& packet) { +// TODO +/*p + try: + expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8 + if len(data) == expected_length: + public_key = data[:RNS.Identity.KEYSIZE//8] + interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8] + tunnel_id_data = public_key+interface_hash + tunnel_id = RNS.Identity.full_hash(tunnel_id_data) + random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8] + + signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length] + signed_data = tunnel_id_data+random_hash + + remote_transport_identity = RNS.Identity(create_keys=False) + remote_transport_identity.load_public_key(public_key) + + if remote_transport_identity.validate(signature, signed_data): + Transport.handle_tunnel(tunnel_id, packet.receiving_interface) + + except Exception as e: + RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG) + RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG) +*/ +} + +/*static*/ void Transport::handle_tunnel(const Bytes& tunnel_id, const Interface& interface) { +// TODO +/*p + expires = time.time() + Transport.DESTINATION_TIMEOUT + if not tunnel_id in Transport.tunnels: + RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG) + paths = {} + tunnel_entry = [tunnel_id, interface, paths, expires] + interface.tunnel_id = tunnel_id + Transport.tunnels[tunnel_id] = tunnel_entry + else: + RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG) + tunnel_entry = Transport.tunnels[tunnel_id] + tunnel_entry[1] = interface + tunnel_entry[3] = expires + interface.tunnel_id = tunnel_id + paths = tunnel_entry[2] + + deprecated_paths = [] + for destination_hash, path_entry in paths.items(): + received_from = path_entry[1] + announce_hops = path_entry[2] + expires = path_entry[3] + random_blobs = path_entry[4] + receiving_interface = interface + packet = path_entry[6] + new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet] + + should_add = False + if destination_hash in Transport.destination_table: + old_entry = Transport.destination_table[destination_hash] + old_hops = old_entry[2] + old_expires = old_entry[3] + if announce_hops <= old_hops or time.time() > old_expires: + should_add = True + else: + RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG) + else: + if time.time() < expires: + should_add = True + else: + RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because it has expired", RNS.LOG_DEBUG) + + if should_add: + Transport.destination_table[destination_hash] = new_entry + RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG) + else: + deprecated_paths.append(destination_hash) + + for deprecated_path in deprecated_paths: + RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG) + paths.pop(deprecated_path) +*/ +} + +/*static*/ void Transport::register_interface(Interface& interface) { + TRACE("Transport: Registering interface " + interface.get_hash().toHex() + " " + interface.toString()); +#if defined(INTERFACES_SET) + _interfaces.insert(interface); +#elif defined(INTERFACES_LIST) + _interfaces.push_back(interface); +#elif defined(INTERFACES_MAP) + _interfaces.insert({interface.get_hash(), interface}); +#endif + // CBA TODO set or add transport as listener on interface to receive incoming packets? +} + +/*static*/ void Transport::deregister_interface(const Interface& interface) { + TRACE("Transport: Deregistering interface " + interface.toString()); +#if defined(INTERFACES_SET) + //for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) { + // if ((*iter).get() == interface) { + // _interfaces.erase(iter); + // TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString()); + // break; + // } + //} + //auto iter = _interfaces.find(interface); + auto iter = _interfaces.find(const_cast(interface)); + if (iter != _interfaces.end()) { + _interfaces.erase(iter); + TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString()); + } +#elif defined(INTERFACES_LIST) + for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) { + if ((*iter).get() == interface) { + _interfaces.erase(iter); + TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString()); + break; + } + } +#elif defined(INTERFACES_MAP) + auto iter = _interfaces.find(interface.get_hash()); + if (iter != _interfaces.end()) { + TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).second.toString()); + _interfaces.erase(iter); + } +#endif +} + +/*static*/ void Transport::register_destination(Destination& destination) { + //TRACE("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + TRACE("Transport: Registering destination " + destination.toString()); + destination.mtu(Type::Reticulum::MTU); + if (destination.direction() == Type::Destination::IN) { +#if defined(DESTINATIONS_SET) + for (auto& registered_destination : _destinations) { + if (destination.hash() == registered_destination.hash()) { + //p raise KeyError("Attempt to register an already registered destination.") + throw std::runtime_error("Attempt to register an already registered destination."); + } + } + + // CBA ACCUMULATES + _destinations.insert(destination); +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(destination.hash()); + if (iter != _destinations.end()) { + //p raise KeyError("Attempt to register an already registered destination.") + throw std::runtime_error("Attempt to register an already registered destination."); + } + + // CBA ACCUMULATES + _destinations.insert({destination.hash(), destination}); +#endif + + if (_owner && _owner.is_connected_to_shared_instance()) { + if (destination.type() == Type::Destination::SINGLE) { + TRACE("Transport:register_destination: Announcing destination " + destination.toString()); + destination.announce({}, true); + } + } + } + else { + TRACE("Transport:register_destination: Skipping registration (not direction IN) of destination " + destination.toString()); + } + +/* +#if defined(DESTINATIONS_SET) + for (const Destination& destination : _destinations) { +#elif defined(DESTINATIONS_MAP) + for (auto& [hash, destination] : _destinations) { +#endif + TRACE("Transport::register_destination: Listed destination " + destination.toString()); + } +*/ + //TRACE("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); +} + +/*static*/ void Transport::deregister_destination(const Destination& destination) { + TRACE("Transport: Deregistering destination " + destination.toString()); +#if defined(DESTINATIONS_SET) + if (_destinations.find(destination) != _destinations.end()) { + _destinations.erase(destination); + TRACE("Transport::deregister_destination: Found and removed destination " + destination.toString()); + } +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(destination.hash()); + if (iter != _destinations.end()) { + _destinations.erase(iter); + TRACE("Transport::deregister_destination: Found and removed destination " + (*iter).second.toString()); + } +#endif +} + +/*static*/ void Transport::register_link(Link& link) { + TRACE("Transport: Registering link " + link.toString()); + if (link.initiator()) { + // CBA ACCUMULATES + _pending_links.insert(link); + } + else { + // CBA ACCUMULATES + _active_links.insert(link); + } +} + +/*static*/ void Transport::activate_link(Link& link) { + TRACE("Transport: Activating link " + link.toString()); + if (_pending_links.find(link) != _pending_links.end()) { + if (link.status() != Type::Link::ACTIVE) { + throw std::runtime_error("Invalid link state for link activation: " + std::to_string(link.status())); + } + _pending_links.erase(link); + // CBA ACCUMULATES + _active_links.insert(link); + link.status(Type::Link::ACTIVE); + } + else { + ERROR("Attempted to activate a link that was not in the pending table"); + } +} + +/* +Registers an announce handler. + +:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example` for more info. +*/ +/*static*/ void Transport::register_announce_handler(HAnnounceHandler handler) { + TRACE("Transport: Registering announce handler " + handler->aspect_filter()); + _announce_handlers.insert(handler); +} + +/* +Deregisters an announce handler. + +:param handler: The announce handler to be deregistered. +*/ +/*static*/ void Transport::deregister_announce_handler(HAnnounceHandler handler) { + TRACE("Transport: Deregistering announce handler " + handler->aspect_filter()); + if (_announce_handlers.find(handler) != _announce_handlers.end()) { + _announce_handlers.erase(handler); + TRACE("Transport::deregister_announce_handler: Found and removed handler" + handler->aspect_filter()); + } +} + +/*static*/ Interface Transport::find_interface_from_hash(const Bytes& interface_hash) { +#if defined(INTERFACES_SET) + for (const Interface& interface : _interfaces) { + if (interface.get_hash() == interface_hash) { + TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString()); + return interface; + } + } +#elif defined(INTERFACES_LIST) + for (Interface& interface : _interfaces) { + if (interface.get_hash() == interface_hash) { + TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString()); + return interface; + } + } +#elif defined(INTERFACES_MAP) + auto iter = _interfaces.find(interface_hash); + if (iter != _interfaces.end()) { + TRACE("Transport::find_interface_from_hash: Found interface " + (*iter).second.toString()); + return (*iter).second; + } +#endif + + return {Type::NONE}; +} + +/*static*/ bool Transport::should_cache_packet(const Packet& packet) { + // TODO: Rework the caching system. It's currently + // not very useful to even cache Resource proofs, + // disabling it for now, until redesigned. + // if packet.context == RNS.Packet.RESOURCE_PRF: + // return True + + return false; +} + +// When caching packets to storage, they are written +// exactly as they arrived over their interface. This +// means that they have not had their hop count +// increased yet! Take note of this when reading from +// the packet cache. +/*static*/ bool Transport::cache_packet(const Packet& packet, bool force_cache /*= false*/) { + TRACE("Checking to see if packet " + packet.get_hash().toHex() + " should be cached"); +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + if (should_cache_packet(packet) || force_cache) { + TRACE("Saving packet " + packet.get_hash().toHex() + " to storage"); + try { + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet.get_hash().toHex().c_str()); + return (Persistence::serialize(packet, packet_cache_path) > 0); + } + catch (std::exception& e) { + ERROR("Error writing packet to cache. The contained exception was: " + std::string(e.what())); + } + } +#endif + return false; +} + +/*static*/ Packet Transport::get_cached_packet(const Bytes& packet_hash) { + TRACE("Loading packet " + packet_hash.toHex() + " from cache storage"); +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + try { +/*p + packet_hash = RNS.hexrep(packet_hash, delimit=False) + path = RNS.Reticulum.cachepath+"/"+packet_hash + + if os.path.isfile(path): + file = open(path, "rb") + cached_data = umsgpack.unpackb(file.read()) + file.close() + + packet = RNS.Packet(None, cached_data[0]) + interface_reference = cached_data[1] + + for interface in Transport.interfaces: + if str(interface) == interface_reference: + packet.receiving_interface = interface + + return packet + else: + return None +*/ + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet_hash.toHex().c_str()); + Packet packet({Type::NONE}); + if (Persistence::deserialize(packet, packet_cache_path) > 0) { + packet.unpack(); + } + return packet; + } + catch (std::exception& e) { + ERROR("Exception occurred while getting cached packet."); + ERRORF("The contained exception was: %s", e.what()); + } +#endif + return {Type::NONE}; +} + +/*static*/ bool Transport::clear_cached_packet(const Bytes& packet_hash) { + TRACE("Clearing packet " + packet_hash.toHex() + " from cache storage"); +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + try { + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet_hash.toHex().c_str()); + double start_time = OS::time(); + bool success = RNS::Utilities::OS::remove_file(packet_cache_path); + double diff_time = OS::time() - start_time; + if (diff_time < 1.0) { + DEBUG("Remove cached packet in " + std::to_string((int)(diff_time*1000)) + " ms"); + } + else { + DEBUG("Remove cached packet in " + std::to_string(diff_time) + " s"); + } + } + catch (std::exception& e) { + ERROR("Exception occurred while clearing cached packet."); + ERRORF("The contained exception was: %s", e.what()); + } +#endif + return false; +} + +/*static*/ bool Transport::cache_request_packet(const Packet& packet) { + if (packet.data().size() == Type::Identity::HASHLENGTH/8) { + const Packet& cached_packet = get_cached_packet(packet.data()); + + if (cached_packet) { + // If the packet was retrieved from the local + // cache, replay it to the Transport instance, + // so that it can be directed towards it original + // destination. + inbound(cached_packet.raw(), cached_packet.receiving_interface()); + return true; + } + else { + return false; + } + } + else { + return false; + } +} + +/*static*/ void Transport::cache_request(const Bytes& packet_hash, const Destination& destination) { + const Packet& cached_packet = get_cached_packet(packet_hash); + if (cached_packet) { + // The packet was found in the local cache, + // replay it to the Transport instance. + inbound(cached_packet.raw(), cached_packet.receiving_interface()); + } + else { + // The packet is not in the local cache, + // query the network. + Packet request(destination, packet_hash, Type::Packet::DATA, Type::Packet::CACHE_REQUEST); + request.send(); + } +} + +/*static*/ bool Transport::remove_path(const Bytes& destination_hash) { + if (_destination_table.erase(destination_hash) > 0) { + // CBA also remove cached announce packet if exists + } + return false; +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: *True* if a path to the destination is known, otherwise *False*. +*/ +/*static*/ bool Transport::has_path(const Bytes& destination_hash) { + if (_destination_table.find(destination_hash) != _destination_table.end()) { + return true; + } + else { + return false; + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown. +*/ +/*static*/ uint8_t Transport::hops_to(const Bytes& destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + return destination_entry._hops; + } + else { + return PATHFINDER_M; + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown. +*/ +/*static*/ Bytes Transport::next_hop(const Bytes& destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + return destination_entry._received_from; + } + else { + return {}; + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown. +*/ +/*static*/ Interface Transport::next_hop_interface(const Bytes& destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + return destination_entry.receiving_interface(); + } + else { + return {Type::NONE}; + } +} + +/*static*/ uint32_t Transport::next_hop_interface_bitrate(const Bytes& destination_hash) { + const Interface& interface = next_hop_interface(destination_hash); + if (interface) { + return interface.bitrate(); + } + else { + return 0; + } +} + +/*static*/ uint16_t Transport::next_hop_interface_hw_mtu(const Bytes& destination_hash) { + const Interface& interface = next_hop_interface(destination_hash); + if (interface) { + if (interface.AUTOCONFIGURE_MTU() || interface.FIXED_MTU()) return interface.HW_MTU(); + else return 0; + } + else { + return 0; + } +} + +/*static*/ double Transport::next_hop_per_bit_latency(const Bytes& destination_hash) { + uint32_t bitrate = next_hop_interface_bitrate(destination_hash); + if (bitrate > 0) { + return (1.0/(double)bitrate); + } + else { + return 0.0; + } +} + +/*static*/ double Transport::next_hop_per_byte_latency(const Bytes& destination_hash) { + double per_bit_latency = next_hop_per_bit_latency(destination_hash); + if (per_bit_latency > 0.0) { + return per_bit_latency*8.0; + } + else { + return 0.0; + } +} + +/*static*/ double Transport::first_hop_timeout(const Bytes& destination_hash) { + double latency = next_hop_per_byte_latency(destination_hash); + if (latency > 0.0) { + return RNS::Type::Reticulum::MTU * latency + RNS::Type::Reticulum::DEFAULT_PER_HOP_TIMEOUT; + } + else { + return RNS::Type::Reticulum::DEFAULT_PER_HOP_TIMEOUT; + } +} + +/*static*/ double Transport::extra_link_proof_timeout(const Interface& interface) { + if (interface) { + return ((1.0/(double)interface.bitrate())*8.0)*RNS::Type::Reticulum::MTU; + } + else { + return 0.0; + } +} + +/*static*/ bool Transport::expire_path(const Bytes& destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + destination_entry._timestamp = 0; + _tables_last_culled = 0; + return true; + } + else { + return false; + } +} + +/*p + + @staticmethod + def mark_path_unresponsive(destination_hash): + if destination_hash in Transport.destination_table: + Transport.path_states[destination_hash] = Transport.STATE_UNRESPONSIVE + return True + else: + return False + + @staticmethod + def mark_path_responsive(destination_hash): + if destination_hash in Transport.destination_table: + Transport.path_states[destination_hash] = Transport.STATE_RESPONSIVE + return True + else: + return False + + @staticmethod + def mark_path_unknown_state(destination_hash): + if destination_hash in Transport.destination_table: + Transport.path_states[destination_hash] = Transport.STATE_UNKNOWN + return True + else: + return False + + @staticmethod + def path_is_unresponsive(destination_hash): + if destination_hash in Transport.path_states: + if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE: + return True + + return False + +*/ + +/* +Requests a path to the destination from the network. If +another reachable peer on the network knows a path, it +will announce it. + +:param destination_hash: A destination hash as *bytes*. +:param on_interface: If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used. +*/ +///*static*/ void Transport::request_path(const Bytes& destination_hash, const Interface& on_interface /*= {Type::NONE}*/, const Bytes& tag /*= {}*/, bool recursive /*= false*/) { +/*static*/ void Transport::request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag /*= {}*/, bool recursive /*= false*/) { + Bytes request_tag; + if (!tag) { + request_tag = Identity::get_random_hash(); + } + else { + request_tag = tag; + } + + Bytes path_request_data; + if (Reticulum::transport_enabled()) { + path_request_data = destination_hash + _identity.hash() + request_tag; + } + else { + path_request_data = destination_hash + request_tag; + } + + Destination path_request_dst({Type::NONE}, Type::Destination::OUT, Type::Destination::PLAIN, Type::Transport::APP_NAME, "path.request"); + Packet packet(path_request_dst, on_interface, path_request_data, Type::Packet::DATA, Type::Packet::CONTEXT_NONE, Type::Transport::BROADCAST, Type::Packet::HEADER_1); + + if (on_interface && recursive) { +// TODO +/*p + if not hasattr(on_interface, "announce_cap"): + on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP + + if not hasattr(on_interface, "announce_allowed_at"): + on_interface.announce_allowed_at = 0 + + if not hasattr(on_interface, "announce_queue"): + on_interface.announce_queue = [] +*/ + + bool queued_announces = (on_interface.announce_queue().size() > 0); + if (queued_announces) { + TRACE("Blocking recursive path request on " + on_interface.toString() + " due to queued announces"); + return; + } + else { + double now = OS::time(); + if (now < on_interface.announce_allowed_at()) { + TRACE("Blocking recursive path request on " + on_interface.toString() + " due to active announce cap"); + return; + } + else { + //p tx_time = ((len(path_request_data)+RNS.Reticulum.HEADER_MINSIZE)*8) / on_interface.bitrate + uint32_t wait_time = 0; + if ( on_interface.bitrate() > 0 && on_interface.announce_cap() > 0) { + uint32_t tx_time = ((path_request_data.size() + Type::Reticulum::HEADER_MINSIZE)*8) / on_interface.bitrate(); + wait_time = (tx_time / on_interface.announce_cap()); + } + const_cast(on_interface).announce_allowed_at(now + wait_time); + } + } + } + + packet.send(); + _path_requests[destination_hash] = OS::time(); +} + +/*static*/ void Transport::request_path(const Bytes& destination_hash) { + return request_path(destination_hash, {Type::NONE}); +} + +/*static*/ void Transport::path_request_handler(const Bytes& data, const Packet& packet) { + TRACE("Transport::path_request_handler"); + try { + // If there is at least bytes enough for a destination + // hash in the packet, we assume those bytes are the + // destination being requested. + if (data.size() >= Type::Identity::TRUNCATED_HASHLENGTH/8) { + Bytes destination_hash = data.left(Type::Identity::TRUNCATED_HASHLENGTH/8); + //TRACE("Transport::path_request_handler: destination_hash: " + destination_hash.toHex()); + // If there is also enough bytes for a transport + // instance ID and at least one tag byte, we + // assume the next bytes to be the trasport ID + // of the requesting transport instance. + Bytes requesting_transport_instance; + if (data.size() > (Type::Identity::TRUNCATED_HASHLENGTH/8)*2) { + requesting_transport_instance = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8, Type::Identity::TRUNCATED_HASHLENGTH/8); + //TRACE("Transport::path_request_handler: requesting_transport_instance: " + requesting_transport_instance.toHex()); + } + + Bytes tag_bytes; + if (data.size() > Type::Identity::TRUNCATED_HASHLENGTH/8*2) { + tag_bytes = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8*2); + } + else if (data.size() > Type::Identity::TRUNCATED_HASHLENGTH/8) { + tag_bytes = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8); + } + + if (tag_bytes) { + //TRACE("Transport::path_request_handler: tag_bytes: " + tag_bytes.toHex()); + if (tag_bytes.size() > Type::Identity::TRUNCATED_HASHLENGTH/8) { + tag_bytes = tag_bytes.left(Type::Identity::TRUNCATED_HASHLENGTH/8); + } + + Bytes unique_tag = destination_hash + tag_bytes; + //TRACE("Transport::path_request_handler: unique_tag: " + unique_tag.toHex()); + + if (_discovery_pr_tags.find(unique_tag) == _discovery_pr_tags.end()) { + // CBA ACCUMULATES + _discovery_pr_tags.insert(unique_tag); + + path_request( + destination_hash, + from_local_client(packet), + packet.receiving_interface(), + requesting_transport_instance, + tag_bytes + ); + } + else { + DEBUG("Ignoring duplicate path request for " + destination_hash.toHex() + " with tag " + unique_tag.toHex()); + } + } + else { + DEBUG("Ignoring tagless path request for " + destination_hash.toHex()); + } + } + } + catch (std::exception& e) { + ERROR("Error while handling path request. The contained exception was: " + std::string(e.what())); + } +} + +/*static*/ void Transport::path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id /*= {}*/, const Bytes& tag /*= {}*/) { + TRACE("Transport::path_request"); + bool should_search_for_unknown = false; + std::string interface_str; + + if (attached_interface) { + if (Reticulum::transport_enabled() && (attached_interface.mode() & Interface::DISCOVER_PATHS_FOR) > 0) { + TRACE("Transport::path_request_handler: interface allows searching for unknown paths"); + should_search_for_unknown = true; + } + + interface_str = " on " + attached_interface.toString(); + } + + DEBUG("Path request for destination " + destination_hash.toHex() + interface_str); + + bool destination_exists_on_local_client = false; + if (_local_client_interfaces.size() > 0) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + TRACE("Transport::path_request_handler: entry found for destination " + destination_hash.toHex()); + DestinationEntry& destination_entry = (*iter).second; + if (is_local_client_interface(destination_entry.receiving_interface())) { + destination_exists_on_local_client = true; + // CBA ACCUMULATES + _pending_local_path_requests.insert({destination_hash, attached_interface}); + } + } + else { + TRACE("Transport::path_request_handler: entry not found for destination " + destination_hash.toHex()); + } + } + + auto destination_iter = _destination_table.find(destination_hash); + //local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None) +#if defined(DESTINATIONS_SET) + Destination local_destination({Type::NONE}); + for (auto& destination : _destinations) { + if (destination.hash() == destination_hash) { + local_destination = destination; + break; + } + } + //if local_destination != None: + if (local_destination) { +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(destination_hash); + if (iter != _destinations.end()) { + auto& local_destination = (*iter).second; +#endif + local_destination.announce({Bytes::NONE}, true, attached_interface, tag); + DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", destination is local to this system"); + } + //p elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table): + else if ((Reticulum::transport_enabled() || is_from_local_client) && destination_iter != _destination_table.end()) { + TRACE("Transport::path_request_handler: entry found for destination " + destination_hash.toHex()); + DestinationEntry& destination_entry = (*destination_iter).second; + const Packet& announce_packet = destination_entry.announce_packet(); + const Bytes& next_hop = destination_entry._received_from; + const Interface& receiving_interface = destination_entry.receiving_interface(); + + if (attached_interface.mode() == Type::Interface::MODE_ROAMING && attached_interface == receiving_interface) { + DEBUG("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface"); + } + else { + if (requestor_transport_id && destination_entry._received_from == requestor_transport_id) { + // TODO: Find a bandwidth efficient way to invalidate our + // known path on this signal. The obvious way of signing + // path requests with transport instance keys is quite + // inefficient. There is probably a better way. Doing + // path invalidation here would decrease the network + // convergence time. Maybe just drop it? + DEBUG("Not answering path request for destination " + destination_hash.toHex() + interface_str + ", since next hop is the requestor"); + } + else { + DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", path is known"); + + double now = OS::time(); + uint8_t retries = Type::Transport::PATHFINDER_R; + uint8_t local_rebroadcasts = 0; + bool block_rebroadcasts = true; + // CBA TODO Determine if okay to take hops directly from DestinationEntry + uint8_t announce_hops = announce_packet.hops(); + + double retransmit_timeout = 0; + if (is_from_local_client) { + retransmit_timeout = now; + } + else { + // TODO: Look at this timing + retransmit_timeout = now + Type::Transport::PATH_REQUEST_GRACE /*+ (RNS.rand() * Transport.PATHFINDER_RW)*/; + } + + // This handles an edge case where a peer sends a past + // request for a destination just after an announce for + // said destination has arrived, but before it has been + // rebroadcast locally. In such a case the actual announce + // is temporarily held, and then reinserted when the path + // request has been served to the peer. + auto announce_iter = _announce_table.find(announce_packet.destination_hash()); + if (announce_iter != _announce_table.end()) { + AnnounceEntry& held_entry = (*announce_iter).second; + // CBA ACCUMULATES + _held_announces.insert({announce_packet.destination_hash(), held_entry}); + } + +/* + // CBA ACCUMULATES + _announce_table.insert({announce_packet.destination_hash(), { + now, + retransmit_timeout, + retries, + // BUG? + //destination_entry.receiving_interface, + destination_entry._received_from, + announce_hops, + announce_packet, + local_rebroadcasts, + block_rebroadcasts, + attached_interface + }}); +*/ + AnnounceEntry announce_entry( + now, + retransmit_timeout, + retries, + // BUG? + //destination_entry.receiving_interface, + destination_entry._received_from, + announce_hops, + announce_packet, + local_rebroadcasts, + block_rebroadcasts, + attached_interface + ); + // CBA ACCUMULATES + _announce_table.insert({announce_packet.destination_hash(), announce_entry}); + } + } + } + else if (is_from_local_client) { + // Forward path request on all interfaces + // except the local client + DEBUG("Forwarding path request from local client for destination " + destination_hash.toHex() + interface_str + " to all other interfaces"); + Bytes request_tag = Identity::get_random_hash(); +#if defined(INTERFACES_SET) + for (const Interface& interface : _interfaces) { +#elif defined(INTERFACES_LIST) + for (Interface& interface : _interfaces) { +#elif defined(INTERFACES_MAP) + for (auto& [hash, interface] : _interfaces) { +#endif + if (interface != attached_interface) { + request_path(destination_hash, interface, request_tag); + } + } + } + else if (should_search_for_unknown) { + TRACE("Transport::path_request_handler: searching for unknown path to " + destination_hash.toHex()); + if (_discovery_path_requests.find(destination_hash) != _discovery_path_requests.end()) { + DEBUG("There is already a waiting path request for destination " + destination_hash.toHex() + " on behalf of path request" + interface_str); + } + else { + // Forward path request on all interfaces + // except the requestor interface + DEBUG("Attempting to discover unknown path to destination " + destination_hash.toHex() + " on behalf of path request" + interface_str); + //p pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface } + //p _discovery_path_requests[destination_hash] = pr_entry; + // CBA ACCUMULATES + _discovery_path_requests.insert({destination_hash, { + destination_hash, + OS::time() + Type::Transport::PATH_REQUEST_TIMEOUT, + attached_interface + }}); + +#if defined(BOUNDARY_MODE) + // BOUNDARY: Track this destination in Whitelist 2 so the path + // response announce from the backbone will be allowed through + _boundary_mentioned_addresses.insert(destination_hash); +#endif + +#if defined(INTERFACES_SET) + for (const Interface& interface : _interfaces) { +#elif defined(INTERFACES_LIST) + for (Interface& interface : _interfaces) { +#elif defined(INTERFACES_MAP) + for (auto& [hash, interface] : _interfaces) { +#endif + // CBA EXPERIMENTAL forwarding path requests even on requestor interface in order to support + // path-finding over LoRa mesh + //if (interface != attached_interface) { + if (true) { + TRACE("Transport::path_request: requesting path on interface " + interface.toString()); + // Use the previously extracted tag from this path request + // on the new path requests as well, to avoid potential loops + request_path(destination_hash, interface, tag, true); + } + else { + TRACE("Transport::path_request: not requesting path on same interface " + interface.toString()); + } + } + } + } + else if (!is_from_local_client && _local_client_interfaces.size() > 0) { + // Forward the path request on all local + // client interfaces + DEBUG("Forwarding path request for destination " + destination_hash.toHex() + interface_str + " to local clients"); + for (const Interface& interface : _local_client_interfaces) { + request_path(destination_hash, interface); + } + } + else { + DEBUG("Ignoring path request for destination " + destination_hash.toHex() + interface_str + ", no path known"); + } +} + +/*static*/ bool Transport::from_local_client(const Packet& packet) { + if (packet.receiving_interface().parent_interface()) { + return is_local_client_interface(packet.receiving_interface()); + } + else { + return false; + } +} + +/*static*/ bool Transport::is_local_client_interface(const Interface& interface) { + if (interface.parent_interface()) { + if (interface.parent_interface()->is_local_shared_instance()) { + return true; + } + else { + return false; + } + } + else { + return false; + } +} + +/*static*/ bool Transport::interface_to_shared_instance(const Interface& interface) { + if (interface.is_connected_to_shared_instance()) { + return true; + } + else { + return false; + } +} + +/*static*/ void Transport::detach_interfaces() { +// TODO +/*p + detachable_interfaces = [] + + for interface in Transport.interfaces: + // Currently no rules are being applied + // here, and all interfaces will be sent + // the detach call on RNS teardown. + if True: + detachable_interfaces.append(interface) + else: + pass + + for interface in Transport.local_client_interfaces: + // Currently no rules are being applied + // here, and all interfaces will be sent + // the detach call on RNS teardown. + if True: + detachable_interfaces.append(interface) + else: + pass + + for interface in detachable_interfaces: + interface.detach() +*/ +} + +/*static*/ void Transport::shared_connection_disappeared() { +// TODO +/*p + for link in Transport.active_links: + link.teardown() + + for link in Transport.pending_links: + link.teardown() + + Transport.announce_table = {} + Transport.destination_table = {} + Transport.reverse_table = {} + Transport.link_table = {} + Transport.held_announces = {} + Transport.announce_handlers = [] + Transport.tunnels = {} +*/ +} + +/*static*/ void Transport::shared_connection_reappeared() { +// TODO +/*p + if Transport.owner.is_connected_to_shared_instance: + for registered_destination in Transport.destinations: + if registered_destination.type == RNS.Destination.SINGLE: + registered_destination.announce(path_response=True) +*/ +} + +/*static*/ void Transport::drop_announce_queues() { +// TODO +/*p + for interface in Transport.interfaces: + if hasattr(interface, "announce_queue") and interface.announce_queue != None: + na = len(interface.announce_queue) + if na > 0: + if na == 1: + na_str = "1 announce" + else: + na_str = str(na)+" announces" + + interface.announce_queue = [] + RNS.log("Dropped "+na_str+" on "+str(interface), RNS.LOG_VERBOSE) +*/ +} + +/*static*/ uint64_t Transport::announce_emitted(const Packet& packet) { + //p random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10] + //p announce_emitted = int.from_bytes(random_blob[5:10], "big") + Bytes random_blob = packet.data().mid(RNS::Type::Identity::KEYSIZE/8+RNS::Type::Identity::NAME_HASH_LENGTH/8, 10); + if (random_blob) { + return OS::from_bytes_big_endian(random_blob.data() + 5, 5); + } + return 0; +} + +/*static*/ void Transport::write_packet_hashlist() { +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) +// TODO +/*p + if not Transport.owner.is_connected_to_shared_instance: + if hasattr(Transport, "saving_packet_hashlist"): + wait_interval = 0.2 + wait_timeout = 5 + wait_start = time.time() + while Transport.saving_packet_hashlist: + time.sleep(wait_interval) + if time.time() > wait_start+wait_timeout: + RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR) + return False + + try: + Transport.saving_packet_hashlist = True + save_start = time.time() + + if not RNS.Reticulum.transport_enabled(): + Transport.packet_hashlist = [] + else: + RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG) + + packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist" + file = open(packet_hashlist_path, "wb") + file.write(umsgpack.packb(Transport.packet_hashlist)) + file.close() + + save_time = time.time() - save_start + if save_time < 1: + time_str = str(round(save_time*1000,2))+"ms" + else: + time_str = str(round(save_time,2))+"s" + RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG) + + except Exception as e: + RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + Transport.saving_packet_hashlist = False +*/ +#endif +} + +//#define CUSTOM 1 + +/*static*/ bool Transport::read_path_table() { + DEBUG("Transport::read_path_table"); +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath); + if (!_owner.is_connected_to_shared_instance() && OS::file_exists(destination_table_path)) { +/*p + serialised_destinations = [] + try: + file = open(destination_table_path, "rb") + serialised_destinations = umsgpack.unpackb(file.read()) + file.close() + + for serialised_entry in serialised_destinations: + destination_hash = serialised_entry[0] + + if len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8: + timestamp = serialised_entry[1] + received_from = serialised_entry[2] + hops = serialised_entry[3] + expires = serialised_entry[4] + random_blobs = serialised_entry[5] + receiving_interface = Transport.find_interface_from_hash(serialised_entry[6]) + announce_packet = Transport.get_cached_packet(serialised_entry[7]) + + if announce_packet != None and receiving_interface != None: + announce_packet.unpack() + // We increase the hops, since reading a packet + // from cache is equivalent to receiving it again + // over an interface. It is cached with it's non- + // increased hop-count. + announce_packet.hops += 1 + Transport.destination_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet] + RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG) + else: + RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) + if announce_packet == None: + RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG) + if receiving_interface == None: + RNS.log("The interface is no longer available", RNS.LOG_DEBUG) +*/ + + try { +#if CUSTOM +TRACEF("Transport::start: buffer capacity %d bytes", Persistence::_buffer.capacity()); + if (RNS::Utilities::OS::read_file(destination_table_path, Persistence::_buffer) > 0) { + TRACEF("Transport::start: read: %d bytes", Persistence::_buffer.size()); +#ifndef NDEBUG + // CBA DEBUG Dump path table +TRACEF("Transport::start: buffer addr: 0x%X", Persistence::_buffer.data()); +TRACEF("Transport::start: buffer size %d bytes", Persistence::_buffer.size()); + //TRACE("SERIALIZED: destination_table"); + //TRACE(Persistence::_buffer.toString()); +#endif +#ifdef USE_MSGPACK + DeserializationError error = deserializeMsgPack(Persistence::_document, Persistence::_buffer.data()); +#else + DeserializationError error = deserializeJson(Persistence::_document, Persistence::_buffer.data()); +#endif + TRACEF("Transport::start: doc size: %d bytes", Persistence::_buffer.size()); + if (!error) { + // Calculate crc for dirty-checking before write + _destination_table_crc = Crc::crc32(0, Persistence::_buffer.data(), Persistence::_buffer.size()); + _destination_table = Persistence::_document.as>(); +#else // CUSTOM + // Calculate crc for dirty-checking before write + if (Persistence::deserialize(_destination_table, destination_table_path, _destination_table_crc) > 0) { +#endif // CUSTOM + + TRACEF("Transport::start: successfully deserialized path table with %d entries", _destination_table.size()); + std::vector invalid_paths; + for (auto& [destination_hash, destination_entry] : _destination_table) { +#ifndef NDEBUG + TRACEF("Transport::start: entry: %s = %s", destination_hash.toHex().c_str(), destination_entry.debugString().c_str()); +#endif + // CBA If announce packet load fails then remove destination entry (it's useless without announce packet) + if (!destination_entry.announce_packet()) { + // remove destination + WARNINGF("Transport::start: removing invalid path to %s due to missing announce packet", destination_hash.toHex().c_str()); + invalid_paths.push_back(destination_hash); + } + // CBA If receiving interface is not found then remove destination entry (it's useless without interface) + if (!destination_entry.receiving_interface()) { + // remove destination + WARNINGF("Transport::start: removing invalid path to %s due to missing receiving interface", destination_hash.toHex().c_str()); + invalid_paths.push_back(destination_hash); + } + } + for (const auto& destination_hash : invalid_paths) { + _destination_table.erase(destination_hash); + } + return true; + } + else { + TRACE("Transport::start: failed to deserialize"); + } +#if CUSTOM + } + else { + TRACE("Transport::start: destination table read failed"); + } +#else // CUSTOM +#endif // CUSTOM + + VERBOSEF("Loaded %d valid path table entries from storage", _destination_table.size()); + + } + catch (std::exception& e) { + ERRORF("Could not load destination table from storage, the contained exception was: %s", e.what()); + } + } +#endif + return false; +} + +/*static*/ bool Transport::write_path_table() { + DEBUG("Transport::write_path_table"); + + if (Transport::_owner.is_connected_to_shared_instance()) { + return true; + } + + bool success = false; +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + if (_saving_path_table) { + double wait_interval = 0.2; + double wait_timeout = 5; + double wait_start = OS::time(); + while (_saving_path_table) { + OS::sleep(wait_interval); + if (OS::time() > (wait_start + wait_timeout)) { + ERROR("Could not save path table to storage, waiting for previous save operation timed out."); + return false; + } + } + } + + try { + _saving_path_table = true; + double save_start = OS::time(); + DEBUGF("Saving %d path table entries to storage...", _destination_table.size()); + +/*p + serialised_destinations = [] + for destination_hash in Transport.destination_table: + // Get the destination entry from the destination table + de = Transport.destination_table[destination_hash] + interface_hash = de[5].get_hash() + + // Only store destination table entry if the associated + // interface is still active + interface = Transport.find_interface_from_hash(interface_hash) + if interface != None: + // Get the destination entry from the destination table + de = Transport.destination_table[destination_hash] + timestamp = de[0] + received_from = de[1] + hops = de[2] + expires = de[3] + random_blobs = de[4] + packet_hash = de[6].get_hash() + + serialised_entry = [ + destination_hash, + timestamp, + received_from, + hops, + expires, + random_blobs, + interface_hash, + packet_hash + ] + + serialised_destinations.append(serialised_entry) + + Transport.cache(de[6], force_cache=True) + + destination_table_path = RNS.Reticulum.storagepath+"/destination_table" + file = open(destination_table_path, "wb") + file.write(umsgpack.packb(serialised_destinations)) + file.close() +*/ + +#if CUSTOM + { + Persistence::_document.set(_destination_table); + TRACEF("Transport::write_path_table: doc size %d bytes", Persistence::_document.memoryUsage()); + + //size_t size = 8192; + size_t size = Persistence::_buffer.capacity(); +TRACE("Transport::write_path_table: obtaining buffer size " + std::to_string(size) + " bytes"); + uint8_t* buffer = Persistence::_buffer.writable(size); +TRACE("Transport::write_path_table: buffer addr: " + std::to_string((long)buffer)); +#ifdef USE_MSGPACK + size_t length = serializeMsgPack(Persistence::_document, buffer, size); +#else + size_t length = serializeJson(Persistence::_document, buffer, size); +#endif + TRACEF("Transport::write_path_table: serialized %d bytes", length); + if (length < size) { + Persistence::_buffer.resize(length); + } + } + if (Persistence::_buffer.size() > 0) { + char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath); +#ifndef NDEBUG + // CBA DEBUG Dump path table +TRACE("Transport::write_path_table: buffer addr: " + std::to_string((long)Persistence::_buffer.data())); +TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence::_buffer.size()) + " bytes"); + //TRACE("SERIALIZED: destination_table"); + //TRACE(Persistence::_buffer.toString()); +#endif + // Check crc to see if data has changed before writing + uint32_t crc = Crc::crc32(0, Persistence::_buffer.data(), Persistence::_buffer.size()); + if (_destination_table_crc > 0 && crc == _destination_table_crc) { + TRACE("Transport::write_path_table: no change detected, skipping write"); + } + else if (RNS::Utilities::OS::write_file(destination_table_path, Persistence::_buffer) == Persistence::_buffer.size()) { + TRACEF("Transport::write_path_table: wrote %d entries, %d bytes", _destination_table.size(), Persistence::_buffer.size()); + _destination_table_crc = crc; + success = true; + +#ifndef NDEBUG + // CBA DEBUG Dump path table + //TRACE("FILE: destination_table"); + //if (OS::read_file("/destination_table", Persistence::_buffer) > 0) { + // TRACE(Persistence::_buffer.toString()); + //} +#endif + } + else { + TRACE("Transport::write_path_table: write failed"); + } + } + else { + TRACE("Transport::write_path_table: failed to serialize"); + } +#else // CUSTOM + uint32_t crc = Persistence::crc(_destination_table); + if (_destination_table_crc > 0 && crc == _destination_table_crc) { + TRACE("Transport::write_path_table: no change detected, skipping write"); + } + else { + TRACE("Transport::write_path_table: change detected, writing..."); + char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath); + if (Persistence::serialize(_destination_table, destination_table_path, _destination_table_crc) > 0) { + TRACEF("Transport::write_path_table: wrote %d entries, %d bytes", _destination_table.size(), Persistence::_buffer.size()); + success = true; + } + } +#endif // CUSTOM + + if (success) { + double save_time = OS::time() - save_start; + if (save_time < 1.0) { + //DEBUG("Saved " + std::to_string(_destination_table.size()) + " path table entries in " + std::to_string(OS::round(save_time * 1000, 1)) + " ms"); + DEBUGF("Saved %d path table entries in %d ms", _destination_table.size(), (int)(save_time*1000)); + } + else { + //DEBUG("Saved " + std::to_string(_destination_table.size()) + " path table entries in " + std::to_string(OS::round(save_time, 1)) + " s"); + DEBUGF("Saved %d path table entries in %d s", _destination_table.size(), save_time); + } + } + } + catch (std::exception& e) { + ERRORF("Could not save path table to storage, the contained exception was: %s", e.what()); + } +#endif + + _saving_path_table = false; + + return success; +} + +/*static*/ void Transport::read_tunnel_table() { + DEBUG("Transport::read_tunnel_table"); +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) +// TODO +/*p + tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels" + if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance: + serialised_tunnels = [] + try: + file = open(tunnel_table_path, "rb") + serialised_tunnels = umsgpack.unpackb(file.read()) + file.close() + + for serialised_tunnel in serialised_tunnels: + tunnel_id = serialised_tunnel[0] + interface_hash = serialised_tunnel[1] + serialised_paths = serialised_tunnel[2] + expires = serialised_tunnel[3] + + tunnel_paths = {} + for serialised_entry in serialised_paths: + destination_hash = serialised_entry[0] + timestamp = serialised_entry[1] + received_from = serialised_entry[2] + hops = serialised_entry[3] + expires = serialised_entry[4] + random_blobs = serialised_entry[5] + receiving_interface = Transport.find_interface_from_hash(serialised_entry[6]) + announce_packet = Transport.get_cached_packet(serialised_entry[7]) + + if announce_packet != None: + announce_packet.unpack() + // We increase the hops, since reading a packet + // from cache is equivalent to receiving it again + // over an interface. It is cached with it's non- + // increased hop-count. + announce_packet.hops += 1 + + tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet] + tunnel_paths[destination_hash] = tunnel_path + + tunnel = [tunnel_id, None, tunnel_paths, expires] + Transport.tunnels[tunnel_id] = tunnel + + if len(Transport.destination_table) == 1: + specifier = "entry" + else: + specifier = "entries" + + RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE) + + except Exception as e: + RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR) +*/ +#endif +} + +/*static*/ void Transport::write_tunnel_table() { +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) +// TODO +/*p + if not Transport.owner.is_connected_to_shared_instance: + if hasattr(Transport, "saving_tunnel_table"): + wait_interval = 0.2 + wait_timeout = 5 + wait_start = time.time() + while Transport.saving_tunnel_table: + time.sleep(wait_interval) + if time.time() > wait_start+wait_timeout: + RNS.log("Could not save tunnel table to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR) + return False + + try: + Transport.saving_tunnel_table = True + save_start = time.time() + RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG) + + serialised_tunnels = [] + for tunnel_id in Transport.tunnels: + te = Transport.tunnels[tunnel_id] + interface = te[1] + tunnel_paths = te[2] + expires = te[3] + + if interface != None: + interface_hash = interface.get_hash() + else: + interface_hash = None + + serialised_paths = [] + for destination_hash in tunnel_paths: + de = tunnel_paths[destination_hash] + + timestamp = de[0] + received_from = de[1] + hops = de[2] + expires = de[3] + random_blobs = de[4] + packet_hash = de[6].get_hash() + + serialised_entry = [ + destination_hash, + timestamp, + received_from, + hops, + expires, + random_blobs, + interface_hash, + packet_hash + ] + + serialised_paths.append(serialised_entry) + + Transport.cache(de[6], force_cache=True) + + + serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires] + serialised_tunnels.append(serialised_tunnel) + + tunnels_path = RNS.Reticulum.storagepath+"/tunnels" + file = open(tunnels_path, "wb") + file.write(umsgpack.packb(serialised_tunnels)) + file.close() + + save_time = time.time() - save_start + if save_time < 1: + time_str = str(round(save_time*1000,2))+" ms" + else: + time_str = str(round(save_time,2))+" s" + RNS.log("Saved "+str(len(serialised_tunnels))+" tunnel table entries in "+time_str, RNS.LOG_DEBUG) + except Exception as e: + RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + Transport.saving_tunnel_table = False +*/ +#endif +} + +/*static*/ void Transport::persist_data() { + TRACE("Transport::persist_data()"); + write_packet_hashlist(); + write_path_table(); + write_tunnel_table(); +} + +/*static*/ void Transport::clean_caches() { + TRACE("Transport::clean_caches()"); +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + // CBA Remove cached packets no longer in path list + std::list files = OS::list_directory(Reticulum::_cachepath); + for (auto& file : files) { + TRACE("Transport::clean_caches: Checking for use of cached packet " + file); + bool found = false; + for (auto& [destination_hash, destination_entry] : _destination_table) { + if (file.compare(destination_entry._announce_packet.toHex()) == 0) { + found = true; + break; + } + } + if (!found) { + TRACE("Transport::clean_caches: No matching path found, removing cached packet " + file); + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, file.c_str()); + OS::remove_file(packet_cache_path); + } + } +#endif +} + +/*static*/ void Transport::dump_stats() { + + OS::dump_heap_stats(); + + size_t memory = OS::heap_available(); + size_t flash = OS::storage_available(); + + if (_last_memory == 0) { + _last_memory = memory; + } + if (_last_flash == 0) { + _last_flash = flash; + } + + // memory + // storage + // _destinations + // _destination_table + // _reverse_table + // _announce_table + // _held_announces + HEADF(LOG_VERBOSE, "mem: %u (%u%%) [%d] flash: %u (%u%%) [%d] paths: %u dsts: %u revr: %u annc: %u held: %u", memory, (int)((double)memory / (double)OS::heap_size() * 100.0), memory - _last_memory, flash, (int)((double)flash / (double)OS::storage_size() * 100.0), flash - _last_flash, _destination_table.size(), _destinations.size(), _reverse_table.size(), _announce_table.size(), _held_announces.size()); + + // _path_requests + // _discovery_path_requests + // _pending_local_path_requests + // _discovery_pr_tags + // _control_destinations + // _control_hashes + VERBOSEF("preqs: %u dpreqs: %u ppreqs: %u dprt: %u cdsts: %u chshs: %u", _path_requests.size(), _discovery_path_requests.size(), _pending_local_path_requests.size(), _discovery_pr_tags.size(), _control_destinations.size(), _control_hashes.size()); + + // _packet_hashlist + // _receipts + // _link_table + // _pending_links + // _active_links + // _tunnels + uint32_t destination_path_responses = 0; + for (auto& [destination_hash, destination] : _destinations) { + destination_path_responses += destination.path_responses().size(); + } + uint32_t interface_announces = 0; + for (auto& [interface_hash, interface] : _interfaces) { + interface_announces += interface.announce_queue().size(); + } + VERBOSEF("phl: %u rcp: %u lt: %u pl: %u al: %u tun: %u", _packet_hashlist.size(), _receipts.size(), _link_table.size(), _pending_links.size(), _active_links.size(), _tunnels.size()); + VERBOSEF("pin: %u pout: %u padd: %u dpr: %u ikd: %u ia: %u\r\n", _packets_received, _packets_sent, _destinations_added, destination_path_responses, Identity::_known_destinations.size(), interface_announces); + + _last_memory = memory; + _last_flash = flash; + +} + +/*static*/ void Transport::exit_handler() { + TRACE("Transport::exit_handler()"); + if (!_owner.is_connected_to_shared_instance()) { + persist_data(); + } +} + +/*static*/ Destination Transport::find_destination_from_hash(const Bytes& destination_hash) { + TRACE("Transport::find_destination_from_hash: Searching for destination " + destination_hash.toHex()); +#if defined(DESTINATIONS_SET) + for (const Destination& destination : _destinations) { + if (destination.get_hash() == destination_hash) { + TRACE("Transport::find_destination_from_hash: Found destination " + destination.toString()); + return destination; + } + } +#elif defined(DESTINATIONS_MAP) + auto iter = _destinations.find(destination_hash); + if (iter != _destinations.end()) { + TRACE("Transport::find_destination_from_hash: Found destination " + (*iter).second.toString()); + return (*iter).second; + } +#endif + + return {Type::NONE}; +} + +/*static*/ void Transport::cull_path_table() { + TRACE("Transport::cull_path_table()"); + if (_destination_table.size() > _path_table_maxsize) { + // TODO prune by age, or better yet by last use +/* + std::map::iterator iter = _destination_table.begin(); + // naively erase from front of table + std::advance(iter, _destination_table.size() - _path_table_maxsize + 1); + _destination_table.erase(_destination_table.begin(), iter); +*/ +/* + uint16_t count = 0; + std::set sorted_values; + MapToValues(_destination_table, sorted_values); + for (auto& destination_entry : sorted_values) { + Packet announce_packet = destination_entry.announce_packet(); + TRACE("Transport::cull_path_table: Removing destination " + announce_packet.destination_hash().toHex() + " from path table"); + // Remove destination from path table + if (_destination_table.erase(announce_packet.destination_hash()) < 1) { + WARNING("Failed to remove destination " + announce_packet.destination_hash().toHex() + " from path table"); + } + // Remove announce packet from packet table + //if (_packet_table.erase(destination_entry._announce_packet) < 1) { + // WARNING("Failed to remove packet " + destination_entry._announce_packet.toHex() + " from packet table"); + //} +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + // Remove cached packet file + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, destination_entry._announce_packet.toHex().c_str()); + if (OS::file_exists(packet_cache_path)) { + OS::remove_file(packet_cache_path); + } +#endif + ++count; + if (_destination_table.size() <= _path_table_maxsize) { + break; + } + } + DEBUG("Removed " + std::to_string(count) + " path(s) from path table"); +*/ + uint16_t count = 0; + std::vector> sorted_pairs; + // Copy key/value pairs from map into vector + std::for_each(_destination_table.begin(), _destination_table.end(), [&](const std::pair& ref) { + sorted_pairs.push_back(ref); + }); + // Sort vector using specified comparator + std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair &left, const std::pair &right) { + return left.second._timestamp < right.second._timestamp; + }); + // Iterate vector of sorted values + for (auto& [destination_hash, destination_entry] : sorted_pairs) { + TRACE("Transport::cull_path_table: Removing destination " + destination_hash.toHex() + " from path table"); + // Remove destination from path table + if (_destination_table.erase(destination_hash) < 1) { + WARNING("Failed to remove destination " + destination_hash.toHex() + " from path table"); + } + // Remove announce packet from packet table + //if (_packet_table.erase(destination_entry._announce_packet) < 1) { + // WARNING("Failed to remove packet " + destination_entry._announce_packet.toHex() + " from packet table"); + //} +#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) + // Remove cached packet file + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, destination_entry._announce_packet.toHex().c_str()); + if (OS::file_exists(packet_cache_path)) { + OS::remove_file(packet_cache_path); + } +#endif + ++count; + if (_destination_table.size() <= _path_table_maxsize) { + break; + } + } + DEBUG("Removed " + std::to_string(count) + " path(s) from path table"); + } +} + +/*static*/ uint16_t Transport::remove_reverse_entries(const std::vector& hashes) { + uint16_t count = 0; + for (const auto& truncated_packet_hash : hashes) { + _reverse_table.erase(truncated_packet_hash); + ++count; + } + if (count > 0) { + TRACEF("Released %u reverse table entries", count); + } + return count; +} + +/*static*/ uint16_t Transport::remove_links(const std::vector& hashes) { + uint16_t count = 0; + for (const auto& link_id : hashes) { + _link_table.erase(link_id); + ++count; + } + if (count > 0) { + TRACEF("Released %u links", count); + } + return count; +} + +/*static*/ uint16_t Transport::remove_paths(const std::vector& hashes) { + uint16_t count = 0; + for (const auto& destination_hash : hashes) { + //_destination_table.erase(destination_hash); + remove_path(destination_hash); + ++count; + } + if (count > 0) { + TRACEF("Released %u paths", count); + } + return count; +} + +/*static*/ uint16_t Transport::remove_discovery_path_requests(const std::vector& hashes) { + uint16_t count = 0; + for (const auto& destination_hash : hashes) { + _discovery_path_requests.erase(destination_hash); + ++count; + } + if (count > 0) { + TRACEF("Released %u waiting path requests", count); + } + return count; +} + +/*static*/ uint16_t Transport::remove_tunnels(const std::vector& hashes) { + uint16_t count = 0; + for (const auto& tunnel_id : hashes) { + _tunnels.erase(tunnel_id); + ++count; + } + if (count > 0) { + TRACEF("Released %u tunnels", count); + } + return count; +} diff --git a/lib/microReticulum/src/Transport.h b/lib/microReticulum/src/Transport.h new file mode 100755 index 0000000..5150cc6 --- /dev/null +++ b/lib/microReticulum/src/Transport.h @@ -0,0 +1,504 @@ +#pragma once + +#include "Packet.h" +#include "Bytes.h" +#include "Type.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +//#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; + + /* + 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& 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 _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& 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 _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 _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 _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 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& hashes); + static uint16_t remove_links(const std::vector& hashes); + static uint16_t remove_paths(const std::vector& hashes); + static uint16_t remove_discovery_path_requests(const std::vector& hashes); + static uint16_t remove_tunnels(const std::vector& 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& get_destination_table() { return _destination_table; } + inline static const std::map& get_announce_rate_table() { return _announce_rate_table; } + inline static const std::map& 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::less> _interfaces; // All active interfaces + static std::set, std::less> _interfaces; // All active interfaces +#elif defined(INTERFACES_LIST) + // list is unsorted, can't use find + static std::list> _interfaces; // All active interfaces +#elif defined(INTERFACES_MAP) + // map is sorted, can use find + static std::map _interfaces; // All active interfaces +#endif +#if defined(DESTINATIONS_SET) + static std::set _destinations; // All active destinations +#elif defined(DESTINATIONS_MAP) + static std::map _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 _pending_links; // Links that are being established + static std::set _active_links; // Links that are active + static std::set _packet_hashlist; // A list of packet hashes for duplicate detection + static std::list _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 _announce_table; // A table for storing announces currently waiting to be retransmitted + static std::map _destination_table; // A lookup table containing the next hop to a given destination + static std::map _reverse_table; // A lookup table for storing packet hashes used to return proofs and replies + static std::map _link_table; // A lookup table containing hops for links + static std::map _held_announces; // A table containing temporarily held announce-table entries + static std::set _announce_handlers; // A table storing externally registered announce handlers + static std::map _tunnels; // A table storing tunnels to other transport instances + static std::map _announce_rate_table; // A table for keeping track of announce rates + static std::map _path_requests; // A table for storing path request timestamps + + static std::map _discovery_path_requests; // A table for keeping track of path requests on behalf of other nodes + static std::set _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 _control_destinations; + static std::set _control_hashes; + + // Interfaces for communicating with + // local clients connected to a shared + // Reticulum instance + //static std::set _local_client_interfaces; + static std::set, std::less> _local_client_interfaces; + + static std::map _pending_local_path_requests; + + // CBA + static std::map _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 + void MapToValues(const M& m, S& s) { + for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) { + s.insert(it->second); + } + } + + template + void MapToPairs(const M& m, S& s) { + for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) { + s.push_back(*it); + } + } +} diff --git a/lib/microReticulum/src/Type.h b/lib/microReticulum/src/Type.h new file mode 100755 index 0000000..4fbf066 --- /dev/null +++ b/lib/microReticulum/src/Type.h @@ -0,0 +1,549 @@ +#pragma once + +#include "Log.h" + +#include +#include + + +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 { + } + +} } diff --git a/lib/microReticulum/src/Utilities/Crc.cpp b/lib/microReticulum/src/Utilities/Crc.cpp new file mode 100755 index 0000000..9798546 --- /dev/null +++ b/lib/microReticulum/src/Utilities/Crc.cpp @@ -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; +} diff --git a/lib/microReticulum/src/Utilities/Crc.h b/lib/microReticulum/src/Utilities/Crc.h new file mode 100755 index 0000000..968f340 --- /dev/null +++ b/lib/microReticulum/src/Utilities/Crc.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +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)); } + + }; + +} } diff --git a/lib/microReticulum/src/Utilities/OS.cpp b/lib/microReticulum/src/Utilities/OS.cpp new file mode 100755 index 0000000..3b2ece6 --- /dev/null +++ b/lib/microReticulum/src/Utilities/OS.cpp @@ -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 +} diff --git a/lib/microReticulum/src/Utilities/OS.h b/lib/microReticulum/src/Utilities/OS.h new file mode 100755 index 0000000..0ccb9d5 --- /dev/null +++ b/lib/microReticulum/src/Utilities/OS.h @@ -0,0 +1,234 @@ +#pragma once + +#include "../FileSystem.h" +#include "../FileStream.h" +#include "../Bytes.h" + +#include "tlsf.h" + +#include +#include +#include +#include +#include + +#ifdef ARDUINO +#include +#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 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(); + + }; + +} } diff --git a/lib/microReticulum/src/Utilities/Persistence.cpp b/lib/microReticulum/src/Utilities/Persistence.cpp new file mode 100755 index 0000000..4c835a3 --- /dev/null +++ b/lib/microReticulum/src/Utilities/Persistence.cpp @@ -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); diff --git a/lib/microReticulum/src/Utilities/Persistence.h b/lib/microReticulum/src/Utilities/Persistence.h new file mode 100755 index 0000000..cdf0c89 --- /dev/null +++ b/lib/microReticulum/src/Utilities/Persistence.h @@ -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 + +#include +#include +#include +#include + +namespace ArduinoJson { + + // ArduinoJSON serialization support for std::vector + template + struct Converter> { + static void toJson(const std::vector& src, JsonVariant dst) { + TRACE("<<< Serializing vector"); + JsonArray array = dst.to(); + for (T item : src) + array.add(item); + TRACE("<<< Finished serializing vector"); + } + static std::vector fromJson(JsonVariantConst src) { + TRACE("<<< Deserializing vector"); + std::vector dst; + for (T item : src.as()) + 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(); + return result; + } + }; + + // ArduinoJSON serialization support for std::set + template + struct Converter> { + static void toJson(const std::set& src, JsonVariant dst) { + JsonArray array = dst.to(); + for (T item : src) + array.add(item); + } + static std::set fromJson(JsonVariantConst src) { + std::set dst; + for (T item : src.as()) + dst.insert(item); + return dst; + } + static bool checkJson(JsonVariantConst src) { + JsonArrayConst array = src; + bool result = array; + for (JsonVariantConst item : array) + result &= item.is(); + return result; + } + }; + + // ArduinoJSON serialization support for std::map + template + struct Converter> { + static void toJson(const std::map& src, JsonVariant dst) { + JsonObject obj = dst.to(); + for (const auto& item : src) + obj[item.first] = item.second; + } + static std::map fromJson(JsonVariantConst src) { + std::map dst; + for (JsonPairConst item : src.as()) + dst[item.key().c_str()] = item.value().as(); + return dst; + } + static bool checkJson(JsonVariantConst src) { + JsonObjectConst obj = src; + bool result = obj; + for (JsonPairConst item : obj) + result &= item.value().is(); + return result; + } + }; + + // ArduinoJSON serialization support for std::map + template + struct Converter> { + static void toJson(const std::map& src, JsonVariant dst) { + JsonObject obj = dst.to(); + for (const auto& item : src) { + //obj[item.first] = item.second; + obj[item.first.toHex()] = item.second; + } + } + static std::map fromJson(JsonVariantConst src) { + std::map dst; + for (JsonPairConst item : src.as()) { + //dst[item.key().c_str()] = item.value().as(); + RNS::Bytes key; + key.assignHex(item.key().c_str()); + //dst[key] = item.value().as(); + dst.insert({key, item.value().as()}); + } + return dst; + } + static bool checkJson(JsonVariantConst src) { + JsonObjectConst obj = src; + bool result = obj; + for (JsonPairConst item : obj) { + result &= item.value().is(); + } + return result; + } + }; + +/* + // ArduinoJSON serialization support for RNS::Bytes + template <> + struct Converter { + 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(); + } + }; +*/ + +/* + // ArduinoJSON serialization support for RNS::Interface + template <> + struct Converter { + 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()); + //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() && strlen(src.as()) == 64; + } + }; +*/ + +/* + // ArduinoJSON serialization support for RNS::Packet + template <> + struct Converter { + 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()); + //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() && strlen(src.as()) == 64; + } + }; +*/ + // ArduinoJSON serialization support for RNS::Packet + template <> + struct Converter { + 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(); + 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() && + src["raw"].is() && + src["sent_at"].is() && + src["destination_hash"].is(); + } + }; + + // ArduinoJSON serialization support for RNS::Transport::PacketEntry + template <> + struct Converter { + 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() && + src["raw"].is() && + src["sent_at"].is() && + src["destination_hash"].is(); + } + }; + +#if 1 + // ArduinoJSON serialization support for RNS::Transport::DestinationEntry + template <> + struct Converter { + 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(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>(); +/* + //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(), + src["received_from"].as(), + src["announce_hops"].as(), + src["expires"].as(), + src["random_blobs"].as>(), + src["receiving_interface"].as(), + src["packet"].as() + ); +*/ + //TRACE(">>> Finished Deserializing Transport::DestinationEntry"); + return dst; + } + static bool checkJson(JsonVariantConst src) { + return + src["timestamp"].is() && + src["received_from"].is() && + src["announce_hops"].is() && + src["expires"].is() && + src["random_blobs"].is>() && + src["interface_hash"].is() && + src["packet_hash"].is(); + } + }; +#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(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>(); +/* + //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(), + src["received_from"].as(), + src["announce_hops"].as(), + src["expires"].as(), + src["random_blobs"].as>(), + src["receiving_interface"].as(), + src["packet"].as() + ); +*/ + //TRACE(">>> Finished Deserializing Transport::DestinationEntry"); + } + + inline bool canConvertFromJson(JsonVariantConst src, const Transport::DestinationEntry&) { + TRACE("=== NEW Checking Transport::DestinationEntry"); + return + src["timestamp"].is() && + src["received_from"].is() && + src["announce_hops"].is() && + src["expires"].is() && + src["random_blobs"].is>() && + src["interface_hash"].is() && + src["packet_hash"].is(); + } +} +#endif + +namespace RNS { namespace Persistence { + + //static DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE); + static JsonDocument _document; + static Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE); + + template size_t crc(const T& obj) { + //TRACE("Persistence::crc"); + _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 size_t serialize(const T& obj, const char* file_path) { + //TRACE("Persistence::serialize"); + _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 size_t deserialize(T& obj, const char* file_path) { + //TRACE("Persistence::deserialize"); + 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(); + // 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 size_t crc(std::map& map) { + //TRACE("Persistence::crc>"); + + 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 size_t serialize(std::map& map, const char* file_path, uint32_t& crc) { + //TRACE("Persistence::serialize>"); + + // 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 size_t serialize(std::map& map, const char* file_path) { + uint32_t crc; + return serialize(map, file_path, crc); + } + + template size_t deserialize(std::map& map, const char* file_path, uint32_t& crc) { + //TRACE("Persistence::deserialize>"); + + // 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(); + // 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 size_t deserialize(std::map& map, const char* file_path) { + uint32_t crc; + return deserialize(map, file_path, crc); + } +#endif + +} } diff --git a/lib/microReticulum/src/Utilities/Print.cpp b/lib/microReticulum/src/Utilities/Print.cpp new file mode 100755 index 0000000..7c2566d --- /dev/null +++ b/lib/microReticulum/src/Utilities/Print.cpp @@ -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 +#include +#include +#include +#include + +#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(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(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 diff --git a/lib/microReticulum/src/Utilities/Print.h b/lib/microReticulum/src/Utilities/Print.h new file mode 100755 index 0000000..89b026c --- /dev/null +++ b/lib/microReticulum/src/Utilities/Print.h @@ -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 +#include +#include + + +#include + +//#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 diff --git a/lib/microReticulum/src/Utilities/Stream.cpp b/lib/microReticulum/src/Utilities/Stream.cpp new file mode 100755 index 0000000..56fc2a6 --- /dev/null +++ b/lib/microReticulum/src/Utilities/Stream.cpp @@ -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 diff --git a/lib/microReticulum/src/Utilities/Stream.h b/lib/microReticulum/src/Utilities/Stream.h new file mode 100755 index 0000000..c3c7a70 --- /dev/null +++ b/lib/microReticulum/src/Utilities/Stream.h @@ -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 +#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 diff --git a/lib/microReticulum/src/Utilities/tlsf.c b/lib/microReticulum/src/Utilities/tlsf.c new file mode 100755 index 0000000..b22156e --- /dev/null +++ b/lib/microReticulum/src/Utilities/tlsf.c @@ -0,0 +1,1268 @@ +#if !defined(ESP32) + +#include +#include +#include +#include +#include +#include + +#include "tlsf.h" + +#if defined(__cplusplus) +#define tlsf_decl inline +#else +#define tlsf_decl static +#endif + +/* +** Architecture-specific bit manipulation routines. +** +** TLSF achieves O(1) cost for malloc and free operations by limiting +** the search for a free block to a free list of guaranteed size +** adequate to fulfill the request, combined with efficient free list +** queries using bitmasks and architecture-specific bit-manipulation +** routines. +** +** Most modern processors provide instructions to count leading zeroes +** in a word, find the lowest and highest set bit, etc. These +** specific implementations will be used when available, falling back +** to a reasonably efficient generic implementation. +** +** NOTE: TLSF spec relies on ffs/fls returning value 0..31. +** ffs/fls return 1-32 by default, returning 0 for error. +*/ + +/* +** Detect whether or not we are building for a 32- or 64-bit (LP/LLP) +** architecture. There is no reliable portable method at compile-time. +*/ +#if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) \ + || defined (_WIN64) || defined (__LP64__) || defined (__LLP64__) +#define TLSF_64BIT +#endif + +/* +** gcc 3.4 and above have builtin support, specialized for architecture. +** Some compilers masquerade as gcc; patchlevel test filters them out. +*/ +#if defined (__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \ + && defined (__GNUC_PATCHLEVEL__) + +#if defined (__SNC__) +/* SNC for Playstation 3. */ + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __builtin_clz(reverse); + return bit - 1; +} + +#else + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + return __builtin_ffs(word) - 1; +} + +#endif + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __builtin_clz(word) : 0; + return bit - 1; +} + +#elif defined (_MSC_VER) && (_MSC_VER >= 1400) && (defined (_M_IX86) || defined (_M_X64)) +/* Microsoft Visual C++ support on x86/X64 architectures. */ + +#include + +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) + +tlsf_decl int tlsf_fls(unsigned int word) +{ + unsigned long index; + return _BitScanReverse(&index, word) ? index : -1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + unsigned long index; + return _BitScanForward(&index, word) ? index : -1; +} + +#elif defined (_MSC_VER) && defined (_M_PPC) +/* Microsoft Visual C++ support on PowerPC architectures. */ + +#include + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = 32 - _CountLeadingZeros(word); + return bit - 1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - _CountLeadingZeros(reverse); + return bit - 1; +} + +#elif defined (__ARMCC_VERSION) +/* RealView Compilation Tools for ARM */ + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __clz(reverse); + return bit - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __clz(word) : 0; + return bit - 1; +} + +#elif defined (__ghs__) +/* Green Hills support for PowerPC */ + +#include + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __CLZ32(reverse); + return bit - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __CLZ32(word) : 0; + return bit - 1; +} + +#else +/* Fall back to generic implementation. */ + +tlsf_decl int tlsf_fls_generic(unsigned int word) +{ + int bit = 32; + + if (!word) bit -= 1; + if (!(word & 0xffff0000)) { word <<= 16; bit -= 16; } + if (!(word & 0xff000000)) { word <<= 8; bit -= 8; } + if (!(word & 0xf0000000)) { word <<= 4; bit -= 4; } + if (!(word & 0xc0000000)) { word <<= 2; bit -= 2; } + if (!(word & 0x80000000)) { word <<= 1; bit -= 1; } + + return bit; +} + +/* Implement ffs in terms of fls. */ +tlsf_decl int tlsf_ffs(unsigned int word) +{ + return tlsf_fls_generic(word & (~word + 1)) - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + return tlsf_fls_generic(word) - 1; +} + +#endif + +/* Possibly 64-bit version of tlsf_fls. */ +#if defined (TLSF_64BIT) +tlsf_decl int tlsf_fls_sizet(size_t size) +{ + int high = (int)(size >> 32); + int bits = 0; + if (high) + { + bits = 32 + tlsf_fls(high); + } + else + { + bits = tlsf_fls((int)size & 0xffffffff); + + } + return bits; +} +#else +#define tlsf_fls_sizet tlsf_fls +#endif + +#undef tlsf_decl + +/* +** Constants. +*/ + +/* Public constants: may be modified. */ +enum tlsf_public +{ + /* log2 of number of linear subdivisions of block sizes. Larger + ** values require more memory in the control structure. Values of + ** 4 or 5 are typical. + */ + SL_INDEX_COUNT_LOG2 = 5, +}; + +/* Private constants: do not modify. */ +enum tlsf_private +{ +#if defined (TLSF_64BIT) + /* All allocation sizes and addresses are aligned to 8 bytes. */ + ALIGN_SIZE_LOG2 = 3, +#else + /* All allocation sizes and addresses are aligned to 4 bytes. */ + ALIGN_SIZE_LOG2 = 2, +#endif + ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2), + + /* + ** We support allocations of sizes up to (1 << FL_INDEX_MAX) bits. + ** However, because we linearly subdivide the second-level lists, and + ** our minimum size granularity is 4 bytes, it doesn't make sense to + ** create first-level lists for sizes smaller than SL_INDEX_COUNT * 4, + ** or (1 << (SL_INDEX_COUNT_LOG2 + 2)) bytes, as there we will be + ** trying to split size ranges into more slots than we have available. + ** Instead, we calculate the minimum threshold size, and place all + ** blocks below that size into the 0th first-level list. + */ + +#if defined (TLSF_64BIT) + /* + ** TODO: We can increase this to support larger sizes, at the expense + ** of more overhead in the TLSF structure. + */ + FL_INDEX_MAX = 32, +#else + FL_INDEX_MAX = 30, +#endif + SL_INDEX_COUNT = (1 << SL_INDEX_COUNT_LOG2), + FL_INDEX_SHIFT = (SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2), + FL_INDEX_COUNT = (FL_INDEX_MAX - FL_INDEX_SHIFT + 1), + + SMALL_BLOCK_SIZE = (1 << FL_INDEX_SHIFT), +}; + +/* +** Cast and min/max macros. +*/ + +#define tlsf_cast(t, exp) ((t) (exp)) +#define tlsf_min(a, b) ((a) < (b) ? (a) : (b)) +#define tlsf_max(a, b) ((a) > (b) ? (a) : (b)) + +/* +** Set assert macro, if it has not been provided by the user. +*/ +#if !defined (tlsf_assert) +#define tlsf_assert assert +#endif + +/* +** Static assertion mechanism. +*/ + +#define _tlsf_glue2(x, y) x ## y +#define _tlsf_glue(x, y) _tlsf_glue2(x, y) +#define tlsf_static_assert(exp) \ + typedef char _tlsf_glue(static_assert, __LINE__) [(exp) ? 1 : -1] + +/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */ +tlsf_static_assert(sizeof(int) * CHAR_BIT == 32); +tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32); +tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64); + +/* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */ +tlsf_static_assert(sizeof(unsigned int) * CHAR_BIT >= SL_INDEX_COUNT); + +/* Ensure we've properly tuned our sizes. */ +tlsf_static_assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT); + +/* +** Data structures and associated constants. +*/ + +/* +** Block header structure. +** +** There are several implementation subtleties involved: +** - The prev_phys_block field is only valid if the previous block is free. +** - The prev_phys_block field is actually stored at the end of the +** previous block. It appears at the beginning of this structure only to +** simplify the implementation. +** - The next_free / prev_free fields are only valid if the block is free. +*/ +typedef struct block_header_t +{ + /* Points to the previous physical block. */ + struct block_header_t* prev_phys_block; + + /* The size of this block, excluding the block header. */ + size_t size; + + /* Next and previous free blocks. */ + struct block_header_t* next_free; + struct block_header_t* prev_free; +} block_header_t; + +/* +** Since block sizes are always at least a multiple of 4, the two least +** significant bits of the size field are used to store the block status: +** - bit 0: whether block is busy or free +** - bit 1: whether previous block is busy or free +*/ +static const size_t block_header_free_bit = 1 << 0; +static const size_t block_header_prev_free_bit = 1 << 1; + +/* +** The size of the block header exposed to used blocks is the size field. +** The prev_phys_block field is stored *inside* the previous free block. +*/ +static const size_t block_header_overhead = sizeof(size_t); + +/* User data starts directly after the size field in a used block. */ +static const size_t block_start_offset = + offsetof(block_header_t, size) + sizeof(size_t); + +/* +** A free block must be large enough to store its header minus the size of +** the prev_phys_block field, and no larger than the number of addressable +** bits for FL_INDEX. +*/ +static const size_t block_size_min = + sizeof(block_header_t) - sizeof(block_header_t*); +static const size_t block_size_max = tlsf_cast(size_t, 1) << FL_INDEX_MAX; + + +/* The TLSF control structure. */ +typedef struct control_t +{ + /* Empty lists point at this block to indicate they are free. */ + block_header_t block_null; + + /* Bitmaps for free lists. */ + unsigned int fl_bitmap; + unsigned int sl_bitmap[FL_INDEX_COUNT]; + + /* Head of free lists. */ + block_header_t* blocks[FL_INDEX_COUNT][SL_INDEX_COUNT]; +} control_t; + +/* A type used for casting when doing pointer arithmetic. */ +typedef ptrdiff_t tlsfptr_t; + +/* +** block_header_t member functions. +*/ + +static size_t block_size(const block_header_t* block) +{ + return block->size & ~(block_header_free_bit | block_header_prev_free_bit); +} + +static void block_set_size(block_header_t* block, size_t size) +{ + const size_t oldsize = block->size; + block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit)); +} + +static int block_is_last(const block_header_t* block) +{ + return block_size(block) == 0; +} + +static int block_is_free(const block_header_t* block) +{ + return tlsf_cast(int, block->size & block_header_free_bit); +} + +static void block_set_free(block_header_t* block) +{ + block->size |= block_header_free_bit; +} + +static void block_set_used(block_header_t* block) +{ + block->size &= ~block_header_free_bit; +} + +static int block_is_prev_free(const block_header_t* block) +{ + return tlsf_cast(int, block->size & block_header_prev_free_bit); +} + +static void block_set_prev_free(block_header_t* block) +{ + block->size |= block_header_prev_free_bit; +} + +static void block_set_prev_used(block_header_t* block) +{ + block->size &= ~block_header_prev_free_bit; +} + +static block_header_t* block_from_ptr(const void* ptr) +{ + return tlsf_cast(block_header_t*, + tlsf_cast(unsigned char*, ptr) - block_start_offset); +} + +static void* block_to_ptr(const block_header_t* block) +{ + return tlsf_cast(void*, + tlsf_cast(unsigned char*, block) + block_start_offset); +} + +/* Return location of next block after block of given size. */ +static block_header_t* offset_to_block(const void* ptr, size_t size) +{ + return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size); +} + +/* Return location of previous block. */ +static block_header_t* block_prev(const block_header_t* block) +{ + tlsf_assert(block_is_prev_free(block) && "previous block must be free"); + return block->prev_phys_block; +} + +/* Return location of next existing block. */ +static block_header_t* block_next(const block_header_t* block) +{ + block_header_t* next = offset_to_block(block_to_ptr(block), + block_size(block) - block_header_overhead); + tlsf_assert(!block_is_last(block)); + return next; +} + +/* Link a new block with its physical neighbor, return the neighbor. */ +static block_header_t* block_link_next(block_header_t* block) +{ + block_header_t* next = block_next(block); + next->prev_phys_block = block; + return next; +} + +static void block_mark_as_free(block_header_t* block) +{ + /* Link the block to the next block, first. */ + block_header_t* next = block_link_next(block); + block_set_prev_free(next); + block_set_free(block); +} + +static void block_mark_as_used(block_header_t* block) +{ + block_header_t* next = block_next(block); + block_set_prev_used(next); + block_set_used(block); +} + +static size_t align_up(size_t x, size_t align) +{ + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return (x + (align - 1)) & ~(align - 1); +} + +static size_t align_down(size_t x, size_t align) +{ + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return x - (x & (align - 1)); +} + +static void* align_ptr(const void* ptr, size_t align) +{ + const tlsfptr_t aligned = + (tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1); + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return tlsf_cast(void*, aligned); +} + +/* +** Adjust an allocation size to be aligned to word size, and no smaller +** than internal minimum. +*/ +static size_t adjust_request_size(size_t size, size_t align) +{ + size_t adjust = 0; + if (size) + { + const size_t aligned = align_up(size, align); + + /* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */ + if (aligned < block_size_max) + { + adjust = tlsf_max(aligned, block_size_min); + } + } + return adjust; +} + +/* +** TLSF utility functions. In most cases, these are direct translations of +** the documentation found in the white paper. +*/ + +static void mapping_insert(size_t size, int* fli, int* sli) +{ + int fl, sl; + if (size < SMALL_BLOCK_SIZE) + { + /* Store small blocks in first list. */ + fl = 0; + sl = tlsf_cast(int, size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT); + } + else + { + fl = tlsf_fls_sizet(size); + sl = tlsf_cast(int, size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2); + fl -= (FL_INDEX_SHIFT - 1); + } + *fli = fl; + *sli = sl; +} + +/* This version rounds up to the next block size (for allocations) */ +static void mapping_search(size_t size, int* fli, int* sli) +{ + if (size >= SMALL_BLOCK_SIZE) + { + const size_t round = (1 << (tlsf_fls_sizet(size) - SL_INDEX_COUNT_LOG2)) - 1; + size += round; + } + mapping_insert(size, fli, sli); +} + +static block_header_t* search_suitable_block(control_t* control, int* fli, int* sli) +{ + int fl = *fli; + int sl = *sli; + + /* + ** First, search for a block in the list associated with the given + ** fl/sl index. + */ + unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl); + if (!sl_map) + { + /* No block exists. Search in the next largest first-level list. */ + const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1)); + if (!fl_map) + { + /* No free blocks available, memory has been exhausted. */ + return 0; + } + + fl = tlsf_ffs(fl_map); + *fli = fl; + sl_map = control->sl_bitmap[fl]; + } + tlsf_assert(sl_map && "internal error - second level bitmap is null"); + sl = tlsf_ffs(sl_map); + *sli = sl; + + /* Return the first block in the free list. */ + return control->blocks[fl][sl]; +} + +/* Remove a free block from the free list.*/ +static void remove_free_block(control_t* control, block_header_t* block, int fl, int sl) +{ + block_header_t* prev = block->prev_free; + block_header_t* next = block->next_free; + tlsf_assert(prev && "prev_free field can not be null"); + tlsf_assert(next && "next_free field can not be null"); + next->prev_free = prev; + prev->next_free = next; + + /* If this block is the head of the free list, set new head. */ + if (control->blocks[fl][sl] == block) + { + control->blocks[fl][sl] = next; + + /* If the new head is null, clear the bitmap. */ + if (next == &control->block_null) + { + control->sl_bitmap[fl] &= ~(1U << sl); + + /* If the second bitmap is now empty, clear the fl bitmap. */ + if (!control->sl_bitmap[fl]) + { + control->fl_bitmap &= ~(1U << fl); + } + } + } +} + +/* Insert a free block into the free block list. */ +static void insert_free_block(control_t* control, block_header_t* block, int fl, int sl) +{ + block_header_t* current = control->blocks[fl][sl]; + tlsf_assert(current && "free list cannot have a null entry"); + tlsf_assert(block && "cannot insert a null entry into the free list"); + block->next_free = current; + block->prev_free = &control->block_null; + current->prev_free = block; + + tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE) + && "block not aligned properly"); + /* + ** Insert the new block at the head of the list, and mark the first- + ** and second-level bitmaps appropriately. + */ + control->blocks[fl][sl] = block; + control->fl_bitmap |= (1U << fl); + control->sl_bitmap[fl] |= (1U << sl); +} + +/* Remove a given block from the free list. */ +static void block_remove(control_t* control, block_header_t* block) +{ + int fl, sl; + mapping_insert(block_size(block), &fl, &sl); + remove_free_block(control, block, fl, sl); +} + +/* Insert a given block into the free list. */ +static void block_insert(control_t* control, block_header_t* block) +{ + int fl, sl; + mapping_insert(block_size(block), &fl, &sl); + insert_free_block(control, block, fl, sl); +} + +static int block_can_split(block_header_t* block, size_t size) +{ + return block_size(block) >= sizeof(block_header_t) + size; +} + +/* Split a block into two, the second of which is free. */ +static block_header_t* block_split(block_header_t* block, size_t size) +{ + /* Calculate the amount of space left in the remaining block. */ + block_header_t* remaining = + offset_to_block(block_to_ptr(block), size - block_header_overhead); + + const size_t remain_size = block_size(block) - (size + block_header_overhead); + + tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE) + && "remaining block not aligned properly"); + + tlsf_assert(block_size(block) == remain_size + size + block_header_overhead); + block_set_size(remaining, remain_size); + tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size"); + + block_set_size(block, size); + block_mark_as_free(remaining); + + return remaining; +} + +/* Absorb a free block's storage into an adjacent previous free block. */ +static block_header_t* block_absorb(block_header_t* prev, block_header_t* block) +{ + tlsf_assert(!block_is_last(prev) && "previous block can't be last"); + /* Note: Leaves flags untouched. */ + prev->size += block_size(block) + block_header_overhead; + block_link_next(prev); + return prev; +} + +/* Merge a just-freed block with an adjacent previous free block. */ +static block_header_t* block_merge_prev(control_t* control, block_header_t* block) +{ + if (block_is_prev_free(block)) + { + block_header_t* prev = block_prev(block); + tlsf_assert(prev && "prev physical block can't be null"); + tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such"); + block_remove(control, prev); + block = block_absorb(prev, block); + } + + return block; +} + +/* Merge a just-freed block with an adjacent free block. */ +static block_header_t* block_merge_next(control_t* control, block_header_t* block) +{ + block_header_t* next = block_next(block); + tlsf_assert(next && "next physical block can't be null"); + + if (block_is_free(next)) + { + tlsf_assert(!block_is_last(block) && "previous block can't be last"); + block_remove(control, next); + block = block_absorb(block, next); + } + + return block; +} + +/* Trim any trailing block space off the end of a block, return to pool. */ +static void block_trim_free(control_t* control, block_header_t* block, size_t size) +{ + tlsf_assert(block_is_free(block) && "block must be free"); + if (block_can_split(block, size)) + { + block_header_t* remaining_block = block_split(block, size); + block_link_next(block); + block_set_prev_free(remaining_block); + block_insert(control, remaining_block); + } +} + +/* Trim any trailing block space off the end of a used block, return to pool. */ +static void block_trim_used(control_t* control, block_header_t* block, size_t size) +{ + tlsf_assert(!block_is_free(block) && "block must be used"); + if (block_can_split(block, size)) + { + /* If the next block is free, we must coalesce. */ + block_header_t* remaining_block = block_split(block, size); + block_set_prev_used(remaining_block); + + remaining_block = block_merge_next(control, remaining_block); + block_insert(control, remaining_block); + } +} + +static block_header_t* block_trim_free_leading(control_t* control, block_header_t* block, size_t size) +{ + block_header_t* remaining_block = block; + if (block_can_split(block, size)) + { + /* We want the 2nd block. */ + remaining_block = block_split(block, size - block_header_overhead); + block_set_prev_free(remaining_block); + + block_link_next(block); + block_insert(control, block); + } + + return remaining_block; +} + +static block_header_t* block_locate_free(control_t* control, size_t size) +{ + int fl = 0, sl = 0; + block_header_t* block = 0; + + if (size) + { + mapping_search(size, &fl, &sl); + + /* + ** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up + ** with indices that are off the end of the block array. + ** So, we protect against that here, since this is the only callsite of mapping_search. + ** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range. + */ + if (fl < FL_INDEX_COUNT) + { + block = search_suitable_block(control, &fl, &sl); + } + } + + if (block) + { + tlsf_assert(block_size(block) >= size); + remove_free_block(control, block, fl, sl); + } + + return block; +} + +static void* block_prepare_used(control_t* control, block_header_t* block, size_t size) +{ + void* p = 0; + if (block) + { + tlsf_assert(size && "size must be non-zero"); + block_trim_free(control, block, size); + block_mark_as_used(block); + p = block_to_ptr(block); + } + return p; +} + +/* Clear structure and point all empty lists at the null block. */ +static void control_construct(control_t* control) +{ + int i, j; + + control->block_null.next_free = &control->block_null; + control->block_null.prev_free = &control->block_null; + + control->fl_bitmap = 0; + for (i = 0; i < FL_INDEX_COUNT; ++i) + { + control->sl_bitmap[i] = 0; + for (j = 0; j < SL_INDEX_COUNT; ++j) + { + control->blocks[i][j] = &control->block_null; + } + } +} + +/* +** Debugging utilities. +*/ + +typedef struct integrity_t +{ + int prev_status; + int status; +} integrity_t; + +#define tlsf_insist(x) { tlsf_assert(x); if (!(x)) { status--; } } + +static void integrity_walker(void* ptr, size_t size, int used, void* user) +{ + block_header_t* block = block_from_ptr(ptr); + integrity_t* integ = tlsf_cast(integrity_t*, user); + const int this_prev_status = block_is_prev_free(block) ? 1 : 0; + const int this_status = block_is_free(block) ? 1 : 0; + const size_t this_block_size = block_size(block); + + int status = 0; + (void)used; + tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect"); + tlsf_insist(size == this_block_size && "block size incorrect"); + + integ->prev_status = this_status; + integ->status += status; +} + +int tlsf_check(tlsf_t tlsf) +{ + int i, j; + + control_t* control = tlsf_cast(control_t*, tlsf); + int status = 0; + + /* Check that the free lists and bitmaps are accurate. */ + for (i = 0; i < FL_INDEX_COUNT; ++i) + { + for (j = 0; j < SL_INDEX_COUNT; ++j) + { + const int fl_map = control->fl_bitmap & (1U << i); + const int sl_list = control->sl_bitmap[i]; + const int sl_map = sl_list & (1U << j); + const block_header_t* block = control->blocks[i][j]; + + /* Check that first- and second-level lists agree. */ + if (!fl_map) + { + tlsf_insist(!sl_map && "second-level map must be null"); + } + + if (!sl_map) + { + tlsf_insist(block == &control->block_null && "block list must be null"); + continue; + } + + /* Check that there is at least one free block. */ + tlsf_insist(sl_list && "no free blocks in second-level map"); + tlsf_insist(block != &control->block_null && "block should not be null"); + + while (block != &control->block_null) + { + int fli, sli; + tlsf_insist(block_is_free(block) && "block should be free"); + tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced"); + tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced"); + tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free"); + tlsf_insist(block_size(block) >= block_size_min && "block not minimum size"); + + mapping_insert(block_size(block), &fli, &sli); + tlsf_insist(fli == i && sli == j && "block size indexed in wrong list"); + block = block->next_free; + } + } + } + + return status; +} + +#undef tlsf_insist + +static void default_walker(void* ptr, size_t size, int used, void* user) +{ + (void)user; + printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr)); +} + +void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user) +{ + tlsf_walker pool_walker = walker ? walker : default_walker; + block_header_t* block = + offset_to_block(pool, -(int)block_header_overhead); + + while (block && !block_is_last(block)) + { + pool_walker( + block_to_ptr(block), + block_size(block), + !block_is_free(block), + user); + block = block_next(block); + } +} + +size_t tlsf_block_size(void* ptr) +{ + size_t size = 0; + if (ptr) + { + const block_header_t* block = block_from_ptr(ptr); + size = block_size(block); + } + return size; +} + +int tlsf_check_pool(pool_t pool) +{ + /* Check that the blocks are physically correct. */ + integrity_t integ = { 0, 0 }; + tlsf_walk_pool(pool, integrity_walker, &integ); + + return integ.status; +} + +/* +** Size of the TLSF structures in a given memory block passed to +** tlsf_create, equal to the size of a control_t +*/ +size_t tlsf_size(void) +{ + return sizeof(control_t); +} + +size_t tlsf_align_size(void) +{ + return ALIGN_SIZE; +} + +size_t tlsf_block_size_min(void) +{ + return block_size_min; +} + +size_t tlsf_block_size_max(void) +{ + return block_size_max; +} + +/* +** Overhead of the TLSF structures in a given memory block passed to +** tlsf_add_pool, equal to the overhead of a free block and the +** sentinel block. +*/ +size_t tlsf_pool_overhead(void) +{ + return 2 * block_header_overhead; +} + +size_t tlsf_alloc_overhead(void) +{ + return block_header_overhead; +} + +pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes) +{ + block_header_t* block; + block_header_t* next; + + const size_t pool_overhead = tlsf_pool_overhead(); + const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE); + + if (((ptrdiff_t)mem % ALIGN_SIZE) != 0) + { + printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n", + (unsigned int)ALIGN_SIZE); + return 0; + } + + if (pool_bytes < block_size_min || pool_bytes > block_size_max) + { +#if defined (TLSF_64BIT) + printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n", + (unsigned int)(pool_overhead + block_size_min), + (unsigned int)((pool_overhead + block_size_max) / 256)); +#else + printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n", + (unsigned int)(pool_overhead + block_size_min), + (unsigned int)(pool_overhead + block_size_max)); +#endif + return 0; + } + + /* + ** Create the main free block. Offset the start of the block slightly + ** so that the prev_phys_block field falls outside of the pool - + ** it will never be used. + */ + block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead); + block_set_size(block, pool_bytes); + block_set_free(block); + block_set_prev_used(block); + block_insert(tlsf_cast(control_t*, tlsf), block); + + /* Split the block to create a zero-size sentinel block. */ + next = block_link_next(block); + block_set_size(next, 0); + block_set_used(next); + block_set_prev_free(next); + + return mem; +} + +void tlsf_remove_pool(tlsf_t tlsf, pool_t pool) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + block_header_t* block = offset_to_block(pool, -(int)block_header_overhead); + + int fl = 0, sl = 0; + + tlsf_assert(block_is_free(block) && "block should be free"); + tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free"); + tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero"); + + mapping_insert(block_size(block), &fl, &sl); + remove_free_block(control, block, fl, sl); +} + +/* +** TLSF main interface. +*/ + +#if _DEBUG +int test_ffs_fls() +{ + /* Verify ffs/fls work properly. */ + int rv = 0; + rv += (tlsf_ffs(0) == -1) ? 0 : 0x1; + rv += (tlsf_fls(0) == -1) ? 0 : 0x2; + rv += (tlsf_ffs(1) == 0) ? 0 : 0x4; + rv += (tlsf_fls(1) == 0) ? 0 : 0x8; + rv += (tlsf_ffs(0x80000000) == 31) ? 0 : 0x10; + rv += (tlsf_ffs(0x80008000) == 15) ? 0 : 0x20; + rv += (tlsf_fls(0x80000008) == 31) ? 0 : 0x40; + rv += (tlsf_fls(0x7FFFFFFF) == 30) ? 0 : 0x80; + +#if defined (TLSF_64BIT) + rv += (tlsf_fls_sizet(0x80000000) == 31) ? 0 : 0x100; + rv += (tlsf_fls_sizet(0x100000000) == 32) ? 0 : 0x200; + rv += (tlsf_fls_sizet(0xffffffffffffffff) == 63) ? 0 : 0x400; +#endif + + if (rv) + { + printf("test_ffs_fls: %x ffs/fls tests failed.\n", rv); + } + return rv; +} +#endif + +tlsf_t tlsf_create(void* mem) +{ +#if _DEBUG + if (test_ffs_fls()) + { + return 0; + } +#endif + + if (((tlsfptr_t)mem % ALIGN_SIZE) != 0) + { + printf("tlsf_create: Memory must be aligned to %u bytes.\n", + (unsigned int)ALIGN_SIZE); + return 0; + } + + control_construct(tlsf_cast(control_t*, mem)); + + return tlsf_cast(tlsf_t, mem); +} + +tlsf_t tlsf_create_with_pool(void* mem, size_t bytes) +{ + tlsf_t tlsf = tlsf_create(mem); + tlsf_add_pool(tlsf, (char*)mem + tlsf_size(), bytes - tlsf_size()); + return tlsf; +} + +void tlsf_destroy(tlsf_t tlsf) +{ + /* Nothing to do. */ + (void)tlsf; +} + +pool_t tlsf_get_pool(tlsf_t tlsf) +{ + return tlsf_cast(pool_t, (char*)tlsf + tlsf_size()); +} + +void* tlsf_malloc(tlsf_t tlsf, size_t size) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + const size_t adjust = adjust_request_size(size, ALIGN_SIZE); + block_header_t* block = block_locate_free(control, adjust); + return block_prepare_used(control, block, adjust); +} + +void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + const size_t adjust = adjust_request_size(size, ALIGN_SIZE); + + /* + ** We must allocate an additional minimum block size bytes so that if + ** our free block will leave an alignment gap which is smaller, we can + ** trim a leading free block and release it back to the pool. We must + ** do this because the previous physical block is in use, therefore + ** the prev_phys_block field is not valid, and we can't simply adjust + ** the size of that block. + */ + const size_t gap_minimum = sizeof(block_header_t); + const size_t size_with_gap = adjust_request_size(adjust + align + gap_minimum, align); + + /* + ** If alignment is less than or equals base alignment, we're done. + ** If we requested 0 bytes, return null, as tlsf_malloc(0) does. + */ + const size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust; + + block_header_t* block = block_locate_free(control, aligned_size); + + /* This can't be a static assert. */ + tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead); + + if (block) + { + void* ptr = block_to_ptr(block); + void* aligned = align_ptr(ptr, align); + size_t gap = tlsf_cast(size_t, + tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); + + /* If gap size is too small, offset to next aligned boundary. */ + if (gap && gap < gap_minimum) + { + const size_t gap_remain = gap_minimum - gap; + const size_t offset = tlsf_max(gap_remain, align); + const void* next_aligned = tlsf_cast(void*, + tlsf_cast(tlsfptr_t, aligned) + offset); + + aligned = align_ptr(next_aligned, align); + gap = tlsf_cast(size_t, + tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); + } + + if (gap) + { + tlsf_assert(gap >= gap_minimum && "gap size too small"); + block = block_trim_free_leading(control, block, gap); + } + } + + return block_prepare_used(control, block, adjust); +} + +void tlsf_free(tlsf_t tlsf, void* ptr) +{ + /* Don't attempt to free a NULL pointer. */ + if (ptr) + { + control_t* control = tlsf_cast(control_t*, tlsf); + block_header_t* block = block_from_ptr(ptr); + tlsf_assert(!block_is_free(block) && "block already marked as free"); + block_mark_as_free(block); + block = block_merge_prev(control, block); + block = block_merge_next(control, block); + block_insert(control, block); + } +} + +/* +** The TLSF block information provides us with enough information to +** provide a reasonably intelligent implementation of realloc, growing or +** shrinking the currently allocated block as required. +** +** This routine handles the somewhat esoteric edge cases of realloc: +** - a non-zero size with a null pointer will behave like malloc +** - a zero size with a non-null pointer will behave like free +** - a request that cannot be satisfied will leave the original buffer +** untouched +** - an extended buffer size will leave the newly-allocated area with +** contents undefined +*/ +void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + void* p = 0; + + /* Zero-size requests are treated as free. */ + if (ptr && size == 0) + { + tlsf_free(tlsf, ptr); + } + /* Requests with NULL pointers are treated as malloc. */ + else if (!ptr) + { + p = tlsf_malloc(tlsf, size); + } + else + { + block_header_t* block = block_from_ptr(ptr); + block_header_t* next = block_next(block); + + const size_t cursize = block_size(block); + const size_t combined = cursize + block_size(next) + block_header_overhead; + const size_t adjust = adjust_request_size(size, ALIGN_SIZE); + + tlsf_assert(!block_is_free(block) && "block already marked as free"); + + /* + ** If the next block is used, or when combined with the current + ** block, does not offer enough space, we must reallocate and copy. + */ + if (adjust > cursize && (!block_is_free(next) || adjust > combined)) + { + p = tlsf_malloc(tlsf, size); + if (p) + { + const size_t minsize = tlsf_min(cursize, size); + memcpy(p, ptr, minsize); + tlsf_free(tlsf, ptr); + } + } + else + { + /* Do we need to expand to the next block? */ + if (adjust > cursize) + { + block_merge_next(control, block); + block_mark_as_used(block); + } + + /* Trim the resulting block and return the original pointer. */ + block_trim_used(control, block, adjust); + p = ptr; + } + } + + return p; +} + +#endif diff --git a/lib/microReticulum/src/Utilities/tlsf.h b/lib/microReticulum/src/Utilities/tlsf.h new file mode 100755 index 0000000..e9b5a91 --- /dev/null +++ b/lib/microReticulum/src/Utilities/tlsf.h @@ -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 + +#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 diff --git a/lib/microReticulum/src/main.cpp b/lib/microReticulum/src/main.cpp new file mode 100755 index 0000000..04cb6c6 --- /dev/null +++ b/lib/microReticulum/src/main.cpp @@ -0,0 +1,23 @@ +// Only include if building locally and NOT testing +#if defined(LIBRARY_TEST) && !defined(PIO_UNIT_TESTING) +#include + +#ifdef ARDUINO +#include + +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 diff --git a/platformio.ini b/platformio.ini index 90f8a59..ea8d598 100755 --- a/platformio.ini +++ b/platformio.ini @@ -61,7 +61,6 @@ lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 adafruit/Adafruit NeoPixel@^1.12.0 - attermann/microReticulum [env:rnode-ng-21] platform = espressif32 @@ -76,7 +75,6 @@ lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 adafruit/Adafruit NeoPixel@^1.12.0 - attermann/microReticulum [env:ttgo-t-beam] platform = espressif32 @@ -90,7 +88,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-t-beam-sx1262] platform = espressif32 @@ -105,7 +102,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-t-beam-supreme] platform = espressif32 @@ -122,7 +118,6 @@ lib_deps = XPowersLib@^0.2.1 Adafruit_SH110X adafruit/Adafruit SH110X@^2.1.14 - attermann/microReticulum [env:lilygo-t3-s3] platform = espressif32 @@ -137,7 +132,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:lilygo-t3-s3-sx127x] platform = espressif32 @@ -152,7 +146,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:lilygo-t3-s3-sx1280-pa] platform = espressif32 @@ -167,7 +160,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:lilygo-t-deck] platform = espressif32 @@ -195,7 +187,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-lora32-v1] platform = espressif32 @@ -209,7 +200,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-lora32-v2] platform = espressif32 @@ -223,7 +213,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-lora32-v2-extled] platform = espressif32 @@ -238,7 +227,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-lora32-v21] platform = espressif32 @@ -252,7 +240,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-lora32-v21-extled] platform = espressif32 @@ -267,7 +254,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:ttgo-lora32-v21-tcxo] platform = espressif32 @@ -282,7 +268,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:heltec_wifi_lora_32_V2] platform = espressif32 @@ -296,7 +281,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:heltec_wifi_lora_32_V2-extled] platform = espressif32 @@ -311,7 +295,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:heltec_wifi_lora_32_V3] platform = espressif32 @@ -325,7 +308,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:heltec_wifi_lora_32_V4] platform = espressif32 @@ -340,7 +322,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:heltec_V4_boundary] platform = espressif32 @@ -371,7 +352,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum monitor_filters = esp32_exception_decoder [env:heltec_V4_boundary-local] @@ -396,7 +376,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - microReticulum=symlink://../microReticulum monitor_filters = esp32_exception_decoder [env:featheresp32] @@ -411,7 +390,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:seeed_xiao_esp32s3] platform = espressif32 @@ -425,7 +403,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:generic-esp32] platform = espressif32 @@ -439,7 +416,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - attermann/microReticulum [env:wiscore_rak4631] platform = nordicnrf52 @@ -457,7 +433,6 @@ build_flags = -DRNS_USE_ALLOCATOR=1 lib_deps = ${env.lib_deps} - attermann/microReticulum @@ -477,7 +452,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - microReticulum=symlink://../microReticulum Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash monitor_filters = esp32_exception_decoder @@ -494,7 +468,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - microReticulum=symlink://../microReticulum monitor_filters = esp32_exception_decoder [env:heltec_wifi_lora_32_V4-local] @@ -510,7 +483,6 @@ build_flags = lib_deps = ${env.lib_deps} XPowersLib@^0.2.1 - microReticulum=symlink://../microReticulum monitor_filters = esp32_exception_decoder [env:wiscore_rak4631-local] @@ -532,7 +504,6 @@ build_flags = build_unflags = -fno-exceptions lib_deps = ${env.lib_deps} - microReticulum=symlink://../microReticulum Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash [env:heltec_t114_local] @@ -554,4 +525,3 @@ lib_deps = ${env.lib_deps} https://github.com/liamcottle/esp8266-oled-ssd1306#e16cee124fe26490cb14880c679321ad8ac89c95 adafruit/Adafruit NeoPixel@^1.12.0 - microReticulum=symlink://../microReticulum