From eef5f1b326ca884a395436637d2f84efb6cd78c2 Mon Sep 17 00:00:00 2001 From: attermann Date: Fri, 6 Oct 2023 17:24:48 -0600 Subject: [PATCH] Initial commit --- .gitignore | 5 + lib/README | 46 +++ platformio.ini | 46 +++ src/Bytes.cpp | 88 ++++++ src/Bytes.h | 199 +++++++++++++ src/Cryptography/Fernet.cpp | 14 + src/Cryptography/Fernet.h | 18 ++ src/Cryptography/Hashes.cpp | 31 ++ src/Cryptography/Hashes.h | 10 + src/Destination.cpp | 347 +++++++++++++++++++++++ src/Destination.h | 166 +++++++++++ src/DumbBuffer.h | 102 +++++++ src/Identity.cpp | 102 +++++++ src/Identity.h | 89 ++++++ src/Interfaces/Interface.cpp | 14 + src/Interfaces/Interface.h | 30 ++ src/Link.cpp | 14 + src/Link.h | 71 +++++ src/Log.cpp | 47 ++++ src/Log.h | 56 ++++ src/Packet.cpp | 532 +++++++++++++++++++++++++++++++++++ src/Packet.h | 187 ++++++++++++ src/Reticulum.cpp | 14 + src/Reticulum.h | 112 ++++++++ src/Transport.cpp | 43 +++ src/Transport.h | 66 +++++ src/main.cpp | 456 ++++++++++++++++++++++++++++++ test/README | 11 + test/test.cpp | 157 +++++++++++ 29 files changed, 3073 insertions(+) create mode 100644 .gitignore create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/Bytes.cpp create mode 100644 src/Bytes.h create mode 100644 src/Cryptography/Fernet.cpp create mode 100644 src/Cryptography/Fernet.h create mode 100644 src/Cryptography/Hashes.cpp create mode 100644 src/Cryptography/Hashes.h create mode 100644 src/Destination.cpp create mode 100644 src/Destination.h create mode 100644 src/DumbBuffer.h create mode 100644 src/Identity.cpp create mode 100644 src/Identity.h create mode 100644 src/Interfaces/Interface.cpp create mode 100644 src/Interfaces/Interface.h create mode 100644 src/Link.cpp create mode 100644 src/Link.h create mode 100644 src/Log.cpp create mode 100644 src/Log.h create mode 100644 src/Packet.cpp create mode 100644 src/Packet.h create mode 100644 src/Reticulum.cpp create mode 100644 src/Reticulum.h create mode 100644 src/Transport.cpp create mode 100644 src/Transport.h create mode 100644 src/main.cpp create mode 100644 test/README create mode 100644 test/test.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..18fc182 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,46 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +;default_envs = native + +[env:avr-test] +platform = atmelavr +board = megaatmega2560 +framework = arduino +debug_tool = simavr + +[env:native] +platform = native +build_flags = + -std=c++11 + -Wall + -Wextra + -Wno-missing-field-initializers + -Wno-format + -Isrc + -DNATIVE +lib_deps = +; operatorfoundation/Crypto@^0.4.0 +lib_compat_mode = off + +[env:ttgo-t-beam] +platform = espressif32 +board = ttgo-t-beam +framework = arduino +monitor_speed = 115200 +build_flags = + -Wall + -Wextra + -Wno-missing-field-initializers + -Wno-format + -Isrc +lib_deps = + operatorfoundation/Crypto@^0.4.0 diff --git a/src/Bytes.cpp b/src/Bytes.cpp new file mode 100644 index 0000000..fa49904 --- /dev/null +++ b/src/Bytes.cpp @@ -0,0 +1,88 @@ +#include "Bytes.h" + +using namespace RNS; + +void Bytes::ownData() { + if (!_data) { + newData(); + } + else if (!_owner) { + Data *data; + if (!_data->empty()) { + //extreme("Bytes is creating a writable copy of its shared data"); + data = new Data(*_data.get()); + } + else { + //extreme("Bytes is creating its own data because shared is empty"); + data = new Data(); + } + _data = SharedData(data); + _owner = true; + } +} + +int8_t 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 { + return 1; + } +} + +char const hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; +char const hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +std::string Bytes::toHex(bool upper /*= true*/) 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; +} + +void Bytes::assignHex(const char* hex) { + // if assignment is empty then clear data and don't bother creating new + if (hex == nullptr || hex[0] == 0) { + _data = nullptr; + return; + } + newData(); + size_t len = strlen(hex); + for (size_t i = 0; i < len; 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 char* hex) { + // if append is empty then do nothing + if (hex == nullptr || hex[0] == 0) { + return; + } + ownData(); + size_t len = strlen(hex); + for (size_t i = 0; i < len; i += 2) { + uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25; + _data->push_back(byte); + } +} diff --git a/src/Bytes.h b/src/Bytes.h new file mode 100644 index 0000000..368a8ff --- /dev/null +++ b/src/Bytes.h @@ -0,0 +1,199 @@ +#pragma once + +#include "Log.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace RNS { + + class Bytes { + + private: + //typedef std::vector Data; + using Data = std::vector; + //typedef std::shared_ptr SharedData; + using SharedData = std::shared_ptr; + + public: + Bytes() { + //extreme("Bytes object created from default, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + } + Bytes(const Bytes &bytes) { + //extreme("Bytes is using shared data"); + _data = bytes.shareData(); + _owner = false; + //extreme("Bytes object copy created from bytes \"" + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + } + Bytes(const uint8_t *chunk, size_t size) { + assign(chunk, size); + //extreme(std::string("Bytes object created from chunk \"") + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + } + Bytes(const char *string) { + assign(string); + //extreme(std::string("Bytes object created from string \"") + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + } + Bytes(const std::string &string) { + assign(string); + //extreme(std::string("Bytes object created from std::string \"") + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + } + ~Bytes() { + //extreme(std::string("Bytes object destroyed \"") + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + } + + inline Bytes& operator = (const Bytes &bytes) { + assign(bytes); + return *this; + } + inline Bytes& operator += (const Bytes &bytes) { + append(bytes); + 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 operator bool() const { + return (_data && !_data->empty()); + } + + private: + inline SharedData shareData() const { + //extreme("Bytes is sharing its own data"); + _owner = false; + return _data; + } + inline void newData(size_t size = 0) { + //extreme("Bytes is creating its own data"); + if (size > 0) { + _data = SharedData(new Data(size)); + } + else { + _data = SharedData(new Data()); + } + _owner = true; + } + void ownData(); + + public: + int8_t compare(const Bytes &bytes) const; + 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 const uint8_t *data() const { if (!_data) return nullptr; return _data->data(); } + + inline std::string toString() const { if (!_data) return ""; return {(const char*)data(), size()}; } + std::string toHex(bool upper = true) const; + + inline uint8_t *writable(size_t size) { + newData(size); + return _data->data(); + } + + inline void assign(const Bytes& bytes) { + // if assignment is empty then clear data and don't bother creating new + if (bytes.size() <= 0) { + _data = nullptr; + return; + } + newData(); + _data->insert(_data->begin(), bytes._data->begin(), bytes._data->end()); + } + inline void assign(const uint8_t *chunk, size_t size) { + // if assignment is empty then clear data and don't bother creating new + if (chunk == nullptr || size <= 0) { + _data = nullptr; + return; + } + newData(); + _data->insert(_data->begin(), 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; + return; + } + newData(); + _data->insert(_data->begin(), (uint8_t *)string, (uint8_t *)string + strlen(string)); + } + inline void assign(const std::string &string) { assign(string.c_str()); } + void assignHex(const char* hex); + + inline void append(const Bytes& bytes) { + // if append is empty then do nothing + if (bytes.size() <= 0) { + return; + } + ownData(); + _data->insert(_data->end(), bytes._data->begin(), bytes._data->end()); + } + inline void append(const uint8_t *chunk, size_t size) { + // if append is empty then do nothing + if (chunk == nullptr || size <= 0) { + return; + } + ownData(); + _data->insert(_data->end(), chunk, chunk + size); + } + inline void append(const char* string) { + // if append is empty then do nothing + if (string == nullptr || string[0] == 0) { + return; + } + ownData(); + _data->insert(_data->end(), (uint8_t *)string, (uint8_t *)string + strlen(string)); + } + inline void append(const std::string &string) { append(string.c_str()); } + inline void append(uint8_t byte) { + ownData(); + _data->push_back(byte); + } + void appendHex(const char* hex); + + private: + SharedData _data; + mutable bool _owner = true; + + }; + + // following array function doesn't work without size since it's past as a pointer to the array sizeof() is of the pointer + //static inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); } + //static inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return Bytes(ptr, len); } + static inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return {ptr, len}; } + //static inline Bytes bytesFromString(const char *str) { return Bytes((uint8_t*)str, strlen(str)); } + static inline Bytes bytesFromString(const char *str) { return {(uint8_t*)str, strlen(str)}; } + //zstatic inline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; } + + static inline std::string stringFromBytes(const Bytes& bytes) { return {(const char*)bytes.data(), bytes.size()}; } + +} + +inline RNS::Bytes& operator << (RNS::Bytes &lhbytes, const RNS::Bytes &rhbytes) { + lhbytes.append(rhbytes); + return lhbytes; +} + +inline RNS::Bytes& operator << (RNS::Bytes &lhbytes, uint8_t rhbyte) { + lhbytes.append(rhbyte); + return lhbytes; +} diff --git a/src/Cryptography/Fernet.cpp b/src/Cryptography/Fernet.cpp new file mode 100644 index 0000000..6c91056 --- /dev/null +++ b/src/Cryptography/Fernet.cpp @@ -0,0 +1,14 @@ +#include "Fernet.h" + +#include "../Log.h" + +using namespace RNS::Cryptography; + +Fernet::Fernet() { + log("Fernet object created", LOG_EXTREME); +} + +Fernet::~Fernet() { + log("Fernet object destroyed", LOG_EXTREME); +} + diff --git a/src/Cryptography/Fernet.h b/src/Cryptography/Fernet.h new file mode 100644 index 0000000..9ec869b --- /dev/null +++ b/src/Cryptography/Fernet.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace RNS { namespace Cryptography { + + class Fernet { + + public: + static const uint8_t FERNET_OVERHEAD = 48; // Bytes + + public: + Fernet(); + ~Fernet(); + + }; + +} } diff --git a/src/Cryptography/Hashes.cpp b/src/Cryptography/Hashes.cpp new file mode 100644 index 0000000..db5ca0b --- /dev/null +++ b/src/Cryptography/Hashes.cpp @@ -0,0 +1,31 @@ +#include "Hashes.h" + +#include "../Log.h" + +#include +#include + +using namespace RNS::Cryptography; + +/* +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. +*/ + +void RNS::Cryptography::sha256(uint8_t *hash, const uint8_t *data, uint16_t data_len) { + //extreme("Cryptography::sha256: data: " + data.toHex() ); + SHA256 digest; + digest.reset(); + digest.update(data, data_len); + digest.finalize(hash, 32); + //extreme("Cryptography::sha256: hash: " + hash.toHex() ); +} + +void RNS::Cryptography::sha512(uint8_t *hash, const uint8_t *data, uint16_t data_len) { + SHA512 digest; + digest.reset(); + digest.update(data, data_len); + digest.finalize(hash, 64); +} diff --git a/src/Cryptography/Hashes.h b/src/Cryptography/Hashes.h new file mode 100644 index 0000000..b400a23 --- /dev/null +++ b/src/Cryptography/Hashes.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace RNS { namespace Cryptography { + + void sha256(uint8_t *hash, const uint8_t *data, uint16_t data_len); + void sha512(uint8_t *hash, const uint8_t *data, uint16_t data_len); + +} } diff --git a/src/Destination.cpp b/src/Destination.cpp new file mode 100644 index 0000000..eba8b55 --- /dev/null +++ b/src/Destination.cpp @@ -0,0 +1,347 @@ +#include "Destination.h" + +#include "Log.h" +#include "Transport.h" +#include "Packet.h" +#include "Interfaces/Interface.h" + +#include +#include +#include +#include + +using namespace RNS; + +Destination::Destination(const Identity &identity, const directions direction, const types type, const char* app_name, const char *aspects) : _object(new Object(identity)) { + assert(_object); + + // Check input values and build name string + if (strchr(app_name, '.') != nullptr) { + throw std::invalid_argument("Dots can't be used in app names"); + } + + _object->_type = type; + _object->_direction = direction; + + std::string fullaspects(aspects); + if (!identity && direction == Destination::IN && _object->_type != Destination::PLAIN) { + debug("Destination::Destination: identity not provided, creating new one"); + _object->_identity = Identity(); + // CBA TODO should following include a "." delimiter? + fullaspects += _object->_identity.hexhash(); + } + debug("Destination::Destination: full aspects: " + fullaspects); + + if (_object->_identity && _object->_type == Destination::PLAIN) { + throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity"); + } + + _object->_name = expand_name(_object->_identity, app_name, fullaspects.c_str()); + debug("Destination::Destination: name: " + _object->_name); + + // Generate the destination address hash + debug("Destination::Destination: creating hash..."); + _object->_hash = hash(_object->_identity, app_name, fullaspects.c_str()); + debug("Destination::Destination: hash: " + _object->_hash.toHex()); + // CBA TEST CRASH + debug("Destination::Destination: creating name hash..."); + _object->_name_hash = Identity::truncated_hash(expand_name(Identity::NONE, app_name, fullaspects.c_str())); + debug("Destination::Destination: name hash: " + _object->_name_hash.toHex()); + _object->_hexhash = _object->_hash.toHex(); + debug("Destination::Destination: hexhash: " + _object->_hexhash); + + debug("Destination::Destination: calling register_destination"); + Transport::register_destination(*this); + + extreme("Destination object created"); +} + +Destination::~Destination() { + extreme("Destination object destroyed"); +} + +/* +: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) { + //name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)] + //addr_hash_material = name_hash + Bytes addr_hash_material = Identity::truncated_hash(expand_name(Identity::NONE, app_name, aspects)); + //if identity != None: + // if isinstance(identity, RNS.Identity): + // addr_hash_material += identity.hash + // elif isinstance(identity, bytes) and len(identity) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8: + // addr_hash_material += identity + // else: + // raise TypeError("Invalid material supplied for destination hash calculation") + addr_hash_material << identity.hash(); + + return Identity::truncated_hash(addr_hash_material); +} + +/* +: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, Interface *attached_interface, const Bytes &tag, bool send) { + assert(_object); + debug("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"); + } + + time_t now = time(nullptr); + auto it = _object->_path_responses.begin(); + while (it != _object->_path_responses.end()) { + // vector + //Response &entry = *it; + // map + Response &entry = (*it).second; + if (now > (entry.first + Destination::PR_TAG_WINDOW)) { + it = _object->_path_responses.erase(it); + } + else { + ++it; + } + } + + Bytes announce_data; + +/* + // CBA TEST + debug("Destination::announce: performing path test..."); + debug("Destination::announce: inserting path..."); + _object->_path_responses.insert({Bytes("foo_tag"), {0, Bytes("this is foo tag")}}); + debug("Destination::announce: inserting path..."); + _object->_path_responses.insert({Bytes("test_tag"), {0, Bytes("this is test tag")}}); + if (path_response) { + debug("Destination::announce: path_response is true"); + } + if (!tag.empty()) { + debug("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())); + debug("Destination::announce: searching for tag..."); + if (_object->_path_responses.find(tag) != _object->_path_responses.end()) { + debug("Destination::announce: found tag in _path_responses"); + debug(std::string("Destination::announce: data: ") +_object->_path_responses[tag].second.toString()); + } + else { + debug("Destination::announce: tag not found in _path_responses"); + } + } + debug("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. + //zextreme("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; + //zBytes random_hash = Identity::get_random_hash()[0:5] << int(time.time()).to_bytes(5, "big"); + Bytes random_hash; + + 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; + debug("Destination::announce: hash: " + _object->_hash.toHex()); + debug("Destination::announce: identity public key: " + _object->_identity.get_public_key().toHex()); + debug("Destination::announce: name hash: " + _object->_name_hash.toHex()); + debug("Destination::announce: random hash: " + random_hash.toHex()); + debug("Destination::announce: new app data: " + new_app_data.toHex()); + signed_data << _object->_hash << _object->_identity.get_public_key() << _object->_name_hash << random_hash; + if (new_app_data) { + signed_data << new_app_data; + } + debug("Destination::announce: signed data: " + signed_data.toHex()); + + Bytes signature(_object->_identity.sign(signed_data)); + debug("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; + } + + _object->_path_responses.insert({tag, {time(nullptr), announce_data}}); + } + debug("Destination::announce: announce_data: " + announce_data.toHex()); + + Packet::context_types announce_context = Packet::CONTEXT_NONE; + if (path_response) { + announce_context = Packet::PATH_RESPONSE; + } + + debug("Destination::announce: creating announce packet..."); + Packet announce_packet(*this, announce_data, Packet::ANNOUNCE, announce_context,Transport::BROADCAST, Packet::HEADER_1, nullptr, attached_interface); + + if (send) { + announce_packet.send(); + return Packet::NONE; + } + else { + return announce_packet; + } +} + +/* + 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. +*/ +void Destination::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. +*/ +void Destination::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. +*/ +void Destination::set_proof_requested_callback(Callbacks::proof_requested callback) { + assert(_object); + _object->_callbacks._proof_requested = callback; +} + +/* +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. +*/ +Bytes Destination::encrypt(const Bytes &data) { + assert(_object); + debug("Destination::encrypt: encrypting bytes"); + +/* + if (_object->_type == Destination::PLAIN) { + return data; + } + + if (_object->_type == Destination::SINGLE && _object->_identity) { + return _object->_identity.encrypt(data) + } + + if (_object->_type == Destination::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?") + } +*/ + + // MOCK + 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. +*/ +Bytes Destination::decrypt(const Bytes &data) { + assert(_object); + debug("Destination::decrypt: decrypting bytes"); + +/* + if (_object->_type == Destination::PLAIN) { + return data; + } + + if (_object->_type == Destination::SINGLE && _object->_identity) { + return identity.decrypt(data); + } + + if (_object->_type == Destination::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 data; +} + +/* +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. +*/ +Bytes Destination::sign(const Bytes &message) { + assert(_object); + if (_object->_type == Destination::SINGLE && _object->_identity) { + return _object->_identity.sign(message); + } + return nullptr; +} diff --git a/src/Destination.h b/src/Destination.h new file mode 100644 index 0000000..e1108f3 --- /dev/null +++ b/src/Destination.h @@ -0,0 +1,166 @@ +#pragma once + +#include "Reticulum.h" +#include "Identity.h" +#include "Bytes.h" + +#include +#include +#include +#include +#include +#include + +namespace RNS { + + class Interface; + class Packet; + class Link; + + /** + * @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, Packet *packet); + using proof_requested = void(*)(Packet *packet); + + link_established _link_established = nullptr; + packet _packet = nullptr; + proof_requested _proof_requested = nullptr; + }; + + //typedef std::pair Response; + using Response = std::pair; + //using Response = std::pair>; + + enum NoneConstructor { + NONE + }; + + // Constants + 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; + + public: + Destination(NoneConstructor none) { + extreme("Destination NONE object created"); + } + Destination(const Destination &destination) : _object(destination._object) { + extreme("Destination object copy created"); + } + Destination(const Identity &identity, const directions direction, const types type, const char* app_name, const char *aspects); + ~Destination(); + + inline Destination& operator = (const Destination &destination) { + _object = destination._object; + extreme("Destination object copy created by assignment, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((uint32_t)_object.get())); + return *this; + } + inline operator bool() const { + return _object.get() != nullptr; + } + + public: + static Bytes hash(const Identity &identity, const char *app_name, const char *aspects); + static std::string expand_name(const Identity &identity, const char *app_name, const char *aspects); + + public: + Packet announce(const Bytes &app_data = {}, bool path_response = false, Interface *attached_interface = nullptr, const Bytes &tag = {}, bool send = true); + inline void set_accepts_links(bool accepts) { assert(_object); _object->_accept_link_requests = accepts; } + inline bool get_accepts_links() { assert(_object); return _object->_accept_link_requests; } + void set_link_established_callback(Callbacks::link_established callback); + void set_packet_callback(Callbacks::packet callback); + void set_proof_requested_callback(Callbacks::proof_requested callback); + Bytes encrypt(const Bytes &data); + Bytes decrypt(const Bytes &data); + Bytes sign(const Bytes &message); + + // getters/setters + inline types type() const { assert(_object); return _object->_type; } + inline directions _direction() const { assert(_object); return _object->_direction; } + inline proof_strategies _proof_strategy() const { assert(_object); return _object->_proof_strategy; } + inline Bytes hash() const { assert(_object); return _object->_hash; } + inline Bytes link_id() const { assert(_object); return _object->_link_id; } + inline uint16_t mtu() const { assert(_object); return _object->_mtu; } + inline void mtu(uint16_t mtu) { assert(_object); _object->_mtu = mtu; } + + private: + class Object { + public: + Object(const Identity &identity) : _identity(identity) {} + private: + bool _accept_link_requests = true; + Callbacks _callbacks; + //z_request_handlers = {} + types _type; + directions _direction; + proof_strategies _proof_strategy = PROVE_NONE; + uint16_t _mtu = 0; + + //std::vector _path_responses; + std::map _path_responses; + //z_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_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; + friend class Destination; + }; + std::shared_ptr _object; + + }; + +} \ No newline at end of file diff --git a/src/DumbBuffer.h b/src/DumbBuffer.h new file mode 100644 index 0000000..5600dc4 --- /dev/null +++ b/src/DumbBuffer.h @@ -0,0 +1,102 @@ +#pragma once + +#include "Log.h" + +#include +#include +#include +#include +#include +#include + +#define COW + +namespace RNS { + + class Bytes { + + public: + Bytes(size_t capacity = 0) { + if (capacity > 0) { + //_vector.reserve(capacity); + _vector = std::shared_ptr>(new std::vector(capacity)); + } + else { + _vector = std::shared_ptr>(new std::vector()); + } + log("Bytes object created from default"); + } + Bytes(const Bytes &bytes, size_t capacity = 0) : Bytes(capacity) { + //append(bytes); + _vector = bytes._vector; + _owner = false; + log(std::string("Bytes object created from bytes \"") + toString() + "\""); + } + Bytes(const uint8_t *chunk, size_t size, size_t capacity = 0) : Bytes(capacity) { + append(chunk, size); + log(std::string("Bytes object created from chunk \"") + toString() + "\""); + } + Bytes(const char *string, size_t capacity = 0) : Bytes(capacity) { + append(string); + log(std::string("Bytes object created from string \"") + toString() + "\""); + } + ~Bytes() { + log(std::string("Bytes object destroyed \"") + toString() + "\""); + } + + inline Bytes& operator + (const Bytes &bytes) { + append(bytes); + return *this; + } + inline Bytes& operator += (const Bytes &bytes) { + append(bytes); + return *this; + } + inline bool operator == (const Bytes &bytes) const { + return _vector == bytes._vector; + } + inline bool operator < (const Bytes &bytes) const { + return _vector < bytes._vector; + } + + public: + inline size_t size() const { return _vector->size(); } + inline bool empty() const { return _vector->empty(); } + inline size_t capacity() const { return _vector->capacity(); } + inline const uint8_t *data() const { return _vector->data(); } + + void append(const Bytes& bytes) { + _vector->insert(_vector->end(), bytes._vector->begin(), bytes._vector->end()); + } + void append(const uint8_t *chunk, size_t size) { + _vector->insert(_vector->end(), chunk, chunk + size); + } + void append(const char* string) { + _vector->insert(_vector->end(), (uint8_t *)string, (uint8_t *)string + strlen(string)); + } + + inline std::string toString() { return {(const char*)data(), size()}; } + + private: + //std::vector _vector; + std::shared_ptr> _vector; + bool _owner = true;; + + }; + + // following array function doesn't work without size since it's past as a pointer to the array sizeof() is of the pointer + //static inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); } + //static inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return Bytes(ptr, len); } + static inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return {ptr, len}; } + //static inline Bytes bytesFromString(const char *str) { return Bytes((uint8_t*)str, strlen(str)); } + static inline Bytes bytesFromString(const char *str) { return {(uint8_t*)str, strlen(str)}; } + //zstatic inline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; } + + static inline std::string stringFromBytes(const Bytes& bytes) { return {(const char*)bytes.data(), bytes.size()}; } + +} + +inline RNS::Bytes& operator << (RNS::Bytes &lhs, const RNS::Bytes &rhs) { + lhs.append(rhs); + return lhs; +} diff --git a/src/Identity.cpp b/src/Identity.cpp new file mode 100644 index 0000000..8a6ce27 --- /dev/null +++ b/src/Identity.cpp @@ -0,0 +1,102 @@ +#include "Identity.h" + +#include "Log.h" +#include "Cryptography/Hashes.h" + +#include + +using namespace RNS; + +Identity::Identity(bool create_keys) : _object(new Object()) { + if (create_keys) { + createKeys(); + } + extreme("Identity object created, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_object.get())); +} + + +void Identity::createKeys() { + assert(_object); + +/* + self.prv = X25519PrivateKey.generate() + self.prv_bytes = self.prv.private_bytes() + + self.sig_prv = Ed25519PrivateKey.generate() + self.sig_prv_bytes = self.sig_prv.private_bytes() + + self.pub = self.prv.public_key() + self.pub_bytes = self.pub.public_bytes() + + self.sig_pub = self.sig_prv.public_key() + self.sig_pub_bytes = self.sig_pub.public_bytes() +*/ + + update_hashes(); + + //verbose("Identity keys created for " + _object->_hash.toHex()); +} + +Bytes Identity::get_public_key() { + assert(_object); + // MOCK + return "abc123"; +} + +void Identity::update_hashes() { + assert(_object); + _object->_hash = truncated_hash(get_public_key()); + debug("Identity::update_hashes: hash: " + _object->_hash.toHex()); + _object->_hexhash = _object->_hash.toHex(); + debug("Identity::update_hashes: hexhash: " + _object->_hexhash); +}; + +/* +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. +*/ +Bytes Identity::sign(const Bytes &message) { + assert(_object); +/* + if self.sig_prv != None: + try: + return self.sig_prv.sign(message) + except Exception as e: + RNS.log("The identity "+str(self)+" could not sign the requested message. The contained exception was: "+str(e), RNS.LOG_ERROR) + raise e + else: + raise KeyError("Signing failed because identity does not hold a private key") +*/ + // MOCK + return {message}; +} + + +/* +Get a SHA-256 hash of passed data. + +:param data: Data to be hashed as *bytes*. +:returns: SHA-256 hash as *bytes* +*/ +/*static*/ Bytes Identity::full_hash(const Bytes &data) { + Bytes hash; + Cryptography::sha256(hash.writable(HASHLENGTH/8), data.data(), data.size()); + //debug("Identity::full_hash: hash: " + hash.toHex()); + return hash; +} + +/* +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*/ Bytes Identity::truncated_hash(const Bytes &data) { + Bytes hash = full_hash(data); + //Bytes truncated_hash(hash.data() + (TRUNCATED_HASHLENGTH/8), TRUNCATED_HASHLENGTH/8); + //debug("Identity::truncated_hash: truncated hash: " + truncated_hash.toHex()); + return Bytes(hash.data() + (TRUNCATED_HASHLENGTH/8), TRUNCATED_HASHLENGTH/8); +} diff --git a/src/Identity.h b/src/Identity.h new file mode 100644 index 0000000..cd4bcc6 --- /dev/null +++ b/src/Identity.h @@ -0,0 +1,89 @@ +#pragma once + +#include "Reticulum.h" +#include "Log.h" +#include "Bytes.h" +#include "Cryptography/Fernet.h" + +#include +#include + +namespace RNS { + + class Identity { + + public: + enum NoneConstructor { + NONE + }; + + //static const char CURVE[] = "Curve25519"; + static constexpr const char* CURVE = "Curve25519"; + // The curve used for Elliptic Curve DH key exchanges + + static const uint16_t KEYSIZE = 256*2; + // X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key. + + // Non-configurable constants + static const uint8_t FERNET_OVERHEAD = RNS::Cryptography::Fernet::FERNET_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 uint16_t TRUNCATED_HASHLENGTH = Reticulum::TRUNCATED_HASHLENGTH; // In bits + // Constant specifying the truncated hash length (in bits) used by Reticulum + // for addressable hashes and other purposes. Non-configurable. + + public: + Identity(NoneConstructor none) { + extreme("Identity NONE object created, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_object.get())); + } + Identity(const Identity &identity) : _object(identity._object) { + extreme("Identity object copy created, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_object.get())); + } + Identity(bool create_keys = true); + ~Identity() { + extreme("Identity object destroyed, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_object.get())); + } + + inline Identity& operator = (const Identity &identity) { + _object = identity._object; + extreme("Identity object copy created by assignment, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((uint32_t)_object.get())); + return *this; + } + inline operator bool() const { + return _object.get() != nullptr; + } + + public: + void createKeys(); + Bytes get_public_key(); + void update_hashes(); + + static Bytes full_hash(const Bytes &data); + static Bytes truncated_hash(const Bytes &data); + Bytes sign(const Bytes &message); + + inline Bytes hash() const { assert(_object); return _object->_hash; } + inline std::string hexhash() const { assert(_object); return _object->_hexhash; } + + private: + class Object { + public: + Object() { + extreme("Identity::Data object created, this: " + std::to_string((ulong)this)); + } + ~Object() { + extreme("Identity::Data object destroyed, this: " + std::to_string((ulong)this)); + } + private: + Bytes _hash; + std::string _hexhash; + friend class Identity; + }; + std::shared_ptr _object; + + }; + +} \ No newline at end of file diff --git a/src/Interfaces/Interface.cpp b/src/Interfaces/Interface.cpp new file mode 100644 index 0000000..7d569c4 --- /dev/null +++ b/src/Interfaces/Interface.cpp @@ -0,0 +1,14 @@ +#include "Interface.h" + +#include "Log.h" + +using namespace RNS; + +Interface::Interface() { + log("Interface object created", LOG_EXTREME); +} + +Interface::~Interface() { + log("Interface object destroyed", LOG_EXTREME); +} + diff --git a/src/Interfaces/Interface.h b/src/Interfaces/Interface.h new file mode 100644 index 0000000..7b726b2 --- /dev/null +++ b/src/Interfaces/Interface.h @@ -0,0 +1,30 @@ +#pragma once + +//#include + +namespace RNS { + + class Interface { + + public: + // Interface mode definitions + enum modes { + MODE_FULL = 0x01, + MODE_POINT_TO_POINT = 0x02, + MODE_ACCESS_POINT = 0x03, + MODE_ROAMING = 0x04, + MODE_BOUNDARY = 0x05, + MODE_GATEWAY = 0x06, + }; + + // Which interface modes a Transport Node + // should actively discover paths for. + //zDISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY] + + public: + Interface(); + ~Interface(); + + }; + +} \ No newline at end of file diff --git a/src/Link.cpp b/src/Link.cpp new file mode 100644 index 0000000..1917f67 --- /dev/null +++ b/src/Link.cpp @@ -0,0 +1,14 @@ +#include "Link.h" + +#include "Log.h" + +using namespace RNS; + +Link::Link() { + log("Link object created", LOG_EXTREME); +} + +Link::~Link() { + log("Link object destroyed", LOG_EXTREME); +} + diff --git a/src/Link.h b/src/Link.h new file mode 100644 index 0000000..f2741f6 --- /dev/null +++ b/src/Link.h @@ -0,0 +1,71 @@ +#pragma once + +#include "Reticulum.h" +#include "Identity.h" + +namespace RNS { + + class Link { + + public: + class Callbacks { + public: + typedef void (*established)(Link *link); + typedef void (*closed)(Link *link); + }; + + static constexpr const char* CURVE = Identity::CURVE; + // The curve used for Elliptic Curve DH key exchanges + + 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; + + static const uint8_t ESTABLISHMENT_TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT; + // Timeout for link establishment in seconds per hop to destination. + + static const uint16_t TRAFFIC_TIMEOUT_FACTOR = 6; + static const uint16_t KEEPALIVE_TIMEOUT_FACTOR = 4; + // RTT timeout factor used in link timeout calculation. + static const uint8_t STALE_GRACE = 2; + // Grace period in seconds used in link timeout calculation. + static const uint16_t KEEPALIVE = 360; + // Interval for sending keep-alive packets on established links in seconds. + static const uint16_t STALE_TIME = 2*KEEPALIVE; + /* + If no traffic or keep-alive packets are received within this period, the + link will be marked as stale, and a final keep-alive packet will be sent. + 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. + */ + + enum status { + PENDING = 0x00, + HANDSHAKE = 0x01, + ACTIVE = 0x02, + STALE = 0x03, + CLOSED = 0x04 + }; + + enum teardown_reasons { + TIMEOUT = 0x01, + INITIATOR_CLOSED = 0x02, + DESTINATION_CLOSED = 0x03, + }; + + enum resource_strategies { + ACCEPT_NONE = 0x00, + ACCEPT_APP = 0x01, + ACCEPT_ALL = 0x02, + }; + + public: + Link(); + ~Link(); + + }; + +} \ No newline at end of file diff --git a/src/Log.cpp b/src/Log.cpp new file mode 100644 index 0000000..32d1467 --- /dev/null +++ b/src/Log.cpp @@ -0,0 +1,47 @@ +#include "Log.h" + +#include + +using namespace RNS; + +const char* getLevelName(LogLevel level) { + switch (level) { + case LOG_CRITICAL: + return "CRITICAL"; + case LOG_ERROR: + return "ERROR"; + case LOG_WARNING: + return "WARNING"; + case LOG_NOTICE: + return "NOTICE"; + case LOG_INFO: + return "INFO"; + case LOG_VERBOSE: + return "VERBOSE"; + case LOG_DEBUG: + return "DEBUG"; + case LOG_EXTREME: + return "EXTRA"; + default: + return ""; + } +} + +LogLevel _level = LOG_VERBOSE; + +void RNS::loglevel(LogLevel level) { + _level = level; +} + +void RNS::doLog(const char* msg, LogLevel level) { + if (level > _level) { + return; + } +#ifndef NATIVE + Serial.print(getLevelName(level)); + Serial.print(" "); + Serial.println(msg); +#else + printf("%s: %s\n", getLevelName(level), msg); +#endif +} diff --git a/src/Log.h b/src/Log.h new file mode 100644 index 0000000..ca68ae7 --- /dev/null +++ b/src/Log.h @@ -0,0 +1,56 @@ +#pragma once + +#ifndef NATIVE +#include +#endif + +#include + +namespace RNS { + + enum LogLevel { + LOG_CRITICAL = 0, + LOG_ERROR = 1, + LOG_WARNING = 2, + LOG_NOTICE = 3, + LOG_INFO = 4, + LOG_VERBOSE = 5, + LOG_DEBUG = 6, + LOG_EXTREME = 7, + }; + + void loglevel(LogLevel level); + + void doLog(const char* msg, LogLevel level); + + inline void log(const char* msg, LogLevel level = LOG_NOTICE) { doLog(msg, level); } +#ifndef NATIVE + inline void log(const String msg, LogLevel level = LOG_NOTICE) { doLog(msg.c_str(), level); } +#endif + inline void log(const std::string& msg, LogLevel level = LOG_NOTICE) { doLog(msg.c_str(), level); } + + inline void critical(const char* msg) { doLog(msg, LOG_CRITICAL); } + inline void critical(const std::string& msg) { doLog(msg.c_str(), LOG_CRITICAL); } + + inline void error(const char* msg) { doLog(msg, LOG_ERROR); } + inline void error(const std::string& msg) { doLog(msg.c_str(), LOG_ERROR); } + + inline void warning(const char* msg) { doLog(msg, LOG_WARNING); } + inline void warning(const std::string& msg) { doLog(msg.c_str(), LOG_WARNING); } + + inline void notice(const char* msg) { doLog(msg, LOG_NOTICE); } + inline void notice(const std::string& msg) { doLog(msg.c_str(), LOG_NOTICE); } + + inline void info(const char* msg) { doLog(msg, LOG_INFO); } + inline void info(const std::string& msg) { doLog(msg.c_str(), LOG_INFO); } + + inline void verbose(const char* msg) { doLog(msg, LOG_VERBOSE); } + inline void verbose(const std::string& msg) { doLog(msg.c_str(), LOG_VERBOSE); } + + inline void debug(const char* msg) { doLog(msg, LOG_DEBUG); } + inline void debug(const std::string& msg) { doLog(msg.c_str(), LOG_DEBUG); } + + inline void extreme(const char* msg) { doLog(msg, LOG_EXTREME); } + inline void extreme(const std::string& msg) { doLog(msg.c_str(), LOG_EXTREME); } + +} diff --git a/src/Packet.cpp b/src/Packet.cpp new file mode 100644 index 0000000..44dbe7a --- /dev/null +++ b/src/Packet.cpp @@ -0,0 +1,532 @@ +#include "Packet.h" + +#include "Identity.h" +#include "Log.h" + +#include +#include + +using namespace RNS; + +Packet::Packet(const Destination &destination, const Bytes &data, types packet_type, context_types context, Transport::types transport_type, header_types header_type, const uint8_t *transport_id, Interface *attached_interface, bool create_receipt) : _object(new Object(destination)) { + assert(_object); + + if (_object->_destination) { + // CBA TODO handle NONE + if (transport_type == -1) { + transport_type = Transport::BROADCAST; + } + // following moved to object constructor to avoid extra NONE object + //_destination = destination; + _header_type = header_type; + _packet_type = packet_type; + _transport_type = transport_type; + _context = context; + + //transport_id = transport_id; + //setTransportId(transport_id); + if (transport_id != nullptr) { + memcpy(_transport_id, transport_id, Reticulum::DESTINATION_LENGTH); + } + + //data = data; + //setData(data); + if (data) { + if (data.size() > MDU) { + _truncated = true; + // CBA TODO add method to truncate + //zdata_len = MDU; + } + memcpy(_data, data.data(), data.size()); + } + _flags = get_packed_flags(); + + _create_receipt = create_receipt; + } + else { + //_raw = data; + if (data) { + memcpy(_raw, data.data(), data.size()); + } + _packed = true; + _fromPacked = true; + _create_receipt = false; + } + _attached_interface = attached_interface; + extreme("Packet object created"); +} + +Packet::~Packet() { + extreme("Packet object destroyed"); +} + + +/* +void Packet::setTransportId(const uint8_t* transport_id) { + if (_transport_id == nullptr) { + delete[] _transport_id; + _transport_id = nullptr; + } + if (transport_id != nullptr) { + _transport_id = new uint8_t[Reticulum::ADDRESS_LENGTH]; + memcpy(_transport_id, transport_id, Reticulum::ADDRESS_LENGTH); + } +} + +void Packet::setTransportId(const uint8_t* header) { + if (_header == nullptr) { + delete[] _header; + _header = nullptr; + } + if (header != nullptr) { + _header = new uint8_t[HEADER_MAXSIZE]; + memcpy(_header, header, HEADER_MAXSIZE); + } +} + +void Packet::setRaw(const uint8_t* raw, uint16_t len) { + if (_raw == nullptr) { + delete[] _raw; + _raw = nullptr; + _raw_len = 0; + } + if (raw != nullptr) { + _raw = new uint8_t[_MTU]; + memcpy(_raw, raw, len); + _raw_len = len; + } +} + +void Packet::setData(const uint8_t* data, uint16_t len) { + if (_data == nullptr) { + delete[] _data; + _data = nullptr; + _data_len = 0; + } + if (data != nullptr) { + _data = new uint8_t[MDU]; + memcpy(_data, data, len); + _data_len = len; + } +} +*/ + + +uint8_t Packet::get_packed_flags() { + uint8_t packed_flags = 0; + if (_context == LRPROOF) { + packed_flags = (_header_type << 6) | (_transport_type << 4) | (Destination::LINK << 2) | _packet_type; + } + else { + packed_flags = (_header_type << 6) | (_transport_type << 4) | (_object->_destination.type() << 2) | _packet_type; + } + return packed_flags; +} + + +/* +== 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); + debug("Packet::pack: packing packet..."); + + //memcpy(_destination_hash, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); + memcpy(_destination_hash, _object->_destination.hash().data(), _object->_destination.hash().size()); + + _header[0] = _flags; + _header[1] = _hops; + + //uint8_t *ciphertext; + if (_context == LRPROOF) { + // write header + //memcpy(_header+2, _destination->_link_id, Reticulum::DESTINATION_LENGTH); + debug("Packet::pack: destination link id: " + _object->_destination.link_id().toHex() ); + memcpy(_header+2, _object->_destination.link_id().data(), _object->_destination.link_id().size()); + _header[Reticulum::DESTINATION_LENGTH+2] = _context; + // prepend header to _data in _raw bytes + memcpy(_data-Reticulum::HEADER_MINSIZE, _header, Reticulum::HEADER_MINSIZE); + //ciphertext = _data; + } + else { + if (_header_type == HEADER_1) { + // write header + //memcpy(_header+2, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); + debug("Packet::pack: destination hash: " + _object->_destination.hash().toHex() ); + memcpy(_header+2, _object->_destination.hash().data(), _object->_destination.hash().size()); + _header[Reticulum::DESTINATION_LENGTH+2] = _context; + // prepend header to _data in _raw bytes + memcpy(_data-Reticulum::HEADER_MINSIZE, _header, Reticulum::HEADER_MINSIZE); + + if (_packet_type == ANNOUNCE) { + // Announce packets are not encrypted + //ciphertext = _data; + } + else if (_packet_type == LINKREQUEST) { + // Link request packets are not encrypted + //ciphertext = _data; + } + else if (_packet_type == PROOF && _context == RESOURCE_PRF) { + // Resource proofs are not encrypted + //ciphertext = _data; + } + else if (_packet_type == PROOF && _object->_destination.type() == Destination::LINK) { + // Packet proofs over links are not encrypted + //ciphertext = _data; + } + else if (_context == RESOURCE) { + // A resource takes care of encryption + // by itself + //ciphertext = _data; + } + else if (_context == KEEPALIVE) { + // Keepalive packets contain no actual + // data + //ciphertext = _data; + } + else if (_context == CACHE_REQUEST) { + // Cache-requests are not encrypted + //ciphertext = _data; + } + else { + // In all other cases, we encrypt the packet + // with the destination's encryption method + // CBA TODO Figure out how to most efficiently pass in data and receive encrypted data back into _raw bytes + // CBA TODO Ensure that encrypted data does not exceed ENCRYPTED_MDU + // CBA TODO Determine if encrypt method can read from and write to the same bytes + //_data_len = _destination->encrypt(_data, _data, _data_len); + //uint8_t data[_data_len]; + //memcpy(data, _data, _data_len); + //_data_len = _destination->encrypt(_data, data, _data_len); + Bytes plaintext(_data, _data_len); + Bytes bytes = _object->_destination.encrypt(plaintext); + _data_len = bytes.size(); + } + } + else if (_header_type == HEADER_2) { + if (memcmp(_transport_id, EMPTY_DESTINATION, Reticulum::DESTINATION_LENGTH) == 0) { + throw std::invalid_argument("Packet with header type 2 must have a transport ID"); + } + // write header + memcpy(_header+2, _transport_id, Reticulum::DESTINATION_LENGTH); + //memcpy(_header+Reticulum::DESTINATION_LENGTH+2, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); + debug("Packet::pack: destination hash: " + _object->_destination.hash().toHex() ); + memcpy(_header+Reticulum::DESTINATION_LENGTH+2, _object->_destination.hash().data(), _object->_destination.hash().size()); + _header[2*Reticulum::DESTINATION_LENGTH+2] = _context; + // prepend header to _data in _raw bytes + memcpy(_data-Reticulum::HEADER_MAXSIZE, _header, Reticulum::HEADER_MAXSIZE); + + if (_packet_type == ANNOUNCE) { + // Announce packets are not encrypted + //ciphertext = _data; + } + } + } + + if (_data_len > _mtu) { + throw std::length_error("Packet size of " + std::to_string(_data_len) + " exceeds MTU of " + std::to_string(_mtu) +" bytes"); + } + + _packed = true; + update_hash(); + +} + +bool Packet::unpack() { + assert(_object); + debug("Packet::unpack: unpacking packet..."); + try { + _flags = _raw[0]; + _hops = _raw[1]; + + _header_type = static_cast((_flags & 0b01000000) >> 6); + _transport_type = static_cast((_flags & 0b00110000) >> 4); + _destination_type = static_cast((_flags & 0b00001100) >> 2); + _packet_type = static_cast(_flags & 0b00000011); + + // CBA TODO detect invalid flags and throw error + if (false) { + log("Received malformed packet, dropping it."); + return false; + } + + if (_header_type == HEADER_2) { + memcpy(_transport_id, _raw+2, Reticulum::DESTINATION_LENGTH); + memcpy(_destination_hash, _raw+Reticulum::DESTINATION_LENGTH+2, Reticulum::DESTINATION_LENGTH); + _context = static_cast(_raw[2*Reticulum::DESTINATION_LENGTH+2]); + _data = _raw+2*Reticulum::DESTINATION_LENGTH+3; + } + else { + //memcpy(_transport_id, EMPTY_DESTINATION, Reticulum::DESTINATION_LENGTH); + memcpy(_destination_hash, _raw+2, Reticulum::DESTINATION_LENGTH); + _context = static_cast(_raw[Reticulum::DESTINATION_LENGTH+2]); + _data = _raw+Reticulum::DESTINATION_LENGTH+3; + } + + _packed = false; + update_hash(); + } + catch (std::exception& ex) { + log(std::string("Received malformed packet, dropping it. The contained exception was: ") + ex.what(), LOG_EXTREME); + 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. +*/ +bool Packet::send() { + assert(_object); + debug("Packet::send: sending packet..."); + if (_sent) { + throw std::logic_error("Packet was already sent"); + } +/* + if (_destination->type == RNS::Destination::LINK) { + if (_destination->status == RNS::Link::CLOSED) { + throw std::runtime_error("Attempt to transmit over a closed link"); + } + else { + _destination->last_outbound = time(); + _destination->tx += 1; + _destination->txbytes += _data_len; + } + } +*/ + + if (!_packed) { + pack(); + } + + if (RNS::Transport::outbound(*this)) { + //zreturn self.receipt + debug("Packet::send: successfully sent packet!!!"); + // MOCK + return true; + } + else { + error("No interfaces could process the outbound packet"); + _sent = false; + //z_receipt = None; + return false; + } +} + +/* +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); + debug("Packet::resend: re-sending packet..."); + if (!_sent) { + throw std::logic_error("Packet was not sent yet"); + } + // Re-pack the packet to obtain new ciphertext for + // encrypted destinations + pack(); + + if (RNS::Transport::outbound(*this)) { + //zreturn self.receipt + debug("Packet::resend: successfully sent packet!!!"); + // MOCK + return true; + } + else { + error("No interfaces could process the outbound packet"); + _sent = false; + //zself.receipt = None; + return false; + } +} + +void Packet::update_hash() { + assert(_object); + _packet_hash = get_hash(); +} + +Bytes Packet::get_hash() { + assert(_object); + Bytes hashable_part = get_hashable_part(); + return Identity::full_hash(hashable_part); +} + +Bytes Packet::getTruncatedHash() { + assert(_object); + Bytes hashable_part = get_hashable_part(); + return Identity::truncated_hash(hashable_part); +} + +Bytes Packet::get_hashable_part() { + assert(_object); + Bytes hashable_part; + hashable_part << (uint8_t)(_raw[0] & 0b00001111); + hashable_part.append(_data-Reticulum::DESTINATION_LENGTH-1, _data_len+Reticulum::DESTINATION_LENGTH+1); + return hashable_part; +} diff --git a/src/Packet.h b/src/Packet.h new file mode 100644 index 0000000..ff3bdd0 --- /dev/null +++ b/src/Packet.h @@ -0,0 +1,187 @@ +#pragma once + +#include "Reticulum.h" +#include "Identity.h" +#include "Transport.h" +#include "Destination.h" +#include "Interfaces/Interface.h" + +#include +#include +#include + +namespace RNS { + + class Packet; + class PacketProof; + class PacketReceipt; + class PacketReceiptCallbacks; + + class Packet { + + public: + enum NoneConstructor { + NONE + }; + + // 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 + }; + + // 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; + + //static constexpr const uint8_t EMPTY_DESTINATION[Reticulum::DESTINATION_LENGTH] = {0}; + uint8_t EMPTY_DESTINATION[Reticulum::DESTINATION_LENGTH] = {0}; + + public: + Packet(const Destination &destination, const Bytes &data, types packet_type = DATA, context_types context = CONTEXT_NONE, Transport::types transport_type = Transport::BROADCAST, header_types header_type = HEADER_1, const uint8_t *transport_id = nullptr, Interface *attached_interface = nullptr, bool create_receipt = true); + Packet(NoneConstructor none) { + extreme("Packet NONE object created"); + } + Packet(const Packet &packet) : _object(packet._object) { + extreme("Packet object copy created"); + } + ~Packet(); + + inline Packet& operator = (const Packet &packet) { + _object = packet._object; + extreme("Packet object copy created by assignment, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((uint32_t)_object.get())); + return *this; + } + inline operator bool() const { + return _object.get() != nullptr; + } + + 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); + */ + + private: + class Object { + public: + Object(const Destination &destination) : _destination(destination) {} + private: + Destination _destination; + friend class Packet; + }; + std::shared_ptr _object; + + public: + uint8_t get_packed_flags(); + void pack(); + bool unpack(); + bool send(); + bool resend(); + void update_hash(); + Bytes get_hash(); + Bytes getTruncatedHash(); + Bytes get_hashable_part(); + + private: + types _packet_type; + header_types _header_type; + context_types _context; + Transport::types _transport_type; + Destination::types _destination_type; + + uint8_t _flags = 0; + uint8_t _hops = 0; + + bool _packed = false; + bool _sent = false; + bool _create_receipt = false; + bool _fromPacked = false; + bool _truncated = false; + //z_receipt = nullptr; + + uint16_t _mtu = Reticulum::MTU; + time_t _sent_at = 0; + + Interface *_attached_interface = nullptr; + Interface *_receiving_interface = nullptr; + + float _rssi = 0.0; + float _snr = 0.0; + + //uint8_t _packet_hash[Reticulum::HASHLENGTH] = {0}; + Bytes _packet_hash; + uint8_t _destination_hash[Reticulum::DESTINATION_LENGTH] = {0}; + uint8_t _transport_id[Reticulum::DESTINATION_LENGTH] = {0}; + + uint8_t _raw[Reticulum::MTU]; + uint8_t _header[Reticulum::HEADER_MAXSIZE]; + uint8_t *_data = _raw + Reticulum::HEADER_MAXSIZE; + uint16_t _data_len = 0; + }; + + + class ProofDestination { + }; + + + class PacketReceipt { + + public: + class Callbacks { + public: + typedef void (*delivery)(PacketReceipt *packet_receipt); + typedef void (*timeout)(PacketReceipt *packet_receipt); + }; + + }; + +} diff --git a/src/Reticulum.cpp b/src/Reticulum.cpp new file mode 100644 index 0000000..a7cead4 --- /dev/null +++ b/src/Reticulum.cpp @@ -0,0 +1,14 @@ +#include "Reticulum.h" + +#include "Log.h" + +using namespace RNS; + +Reticulum::Reticulum() : _object(new Object()) { + extreme("Reticulum object created"); +} + +Reticulum::~Reticulum() { + extreme("Reticulum object destroyed"); +} + diff --git a/src/Reticulum.h b/src/Reticulum.h new file mode 100644 index 0000000..7ce2314 --- /dev/null +++ b/src/Reticulum.h @@ -0,0 +1,112 @@ +#pragma once + +#include "Log.h" + +#include +#include +#include + +namespace RNS { + + class Reticulum { + + private: + class Object { + private: + friend class Reticulum; + }; + + public: + enum NoneConstructor { + NONE + }; + + // 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; + /* + 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. + */ + + 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; + //zIFAC_SALT = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8") + + static const uint16_t MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE; + + static const uint32_t RESOURCE_CACHE = 24*60*60; + static const uint16_t JOB_INTERVAL = 5*60; + static const uint16_t CLEAN_INTERVAL = 15*60; + static const uint16_t PERSIST_INTERVAL = 60*60*12; + static const uint16_t GRACIOUS_PERSIST_INTERVAL = 60*5; + + static const uint8_t DESTINATION_LENGTH = TRUNCATED_HASHLENGTH/8; // In bytes + + public: + Reticulum(); + Reticulum(NoneConstructor none) { + extreme("Reticulum NONE object created"); + } + Reticulum(const Reticulum &reticulum) : _object(reticulum._object) { + extreme("Reticulum object copy created"); + } + ~Reticulum(); + + inline Reticulum& operator = (const Reticulum &reticulum) { + _object = reticulum._object; + extreme("Reticulum object copy created by assignment, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((uint32_t)_object.get())); + return *this; + } + inline operator bool() const { + return _object.get() != nullptr; + } + + private: + std::shared_ptr _object; + + }; + +} diff --git a/src/Transport.cpp b/src/Transport.cpp new file mode 100644 index 0000000..1304e5f --- /dev/null +++ b/src/Transport.cpp @@ -0,0 +1,43 @@ +#include "Transport.h" + +#include "Destination.h" + +#include "Log.h" + +using namespace RNS; + +Transport::Transport() { + log("Transport object created", LOG_EXTREME); +} + +Transport::~Transport() { + log("Transport object destroyed", LOG_EXTREME); +} + + +/*static*/ void Transport::register_destination(Destination &destination) { + destination.mtu(Reticulum::MTU); +/* + if (destination->direction == Destination::IN) { + for (registered_destination in Transport.destinations) { + if (destination->hash == registered_destination->hash) { + raise KeyError("Attempt to register an already registered destination.") + throw std::runtime_error("Attempt to register an already registered destination."); + } + } + + Transport.destinations.append(destination); + + if (Transport.owner.is_connected_to_shared_instance) { + if (destination->type == Destination::SINGLE) { + destination->announce(path_response=True); + } + } + } +*/ +} + +/*static*/ bool Transport::outbound(const Packet &packet) { + // mock + return true; +} diff --git a/src/Transport.h b/src/Transport.h new file mode 100644 index 0000000..f0931c0 --- /dev/null +++ b/src/Transport.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Link.h" + + +namespace RNS { + + class Packet; + class Destination; + + class Transport { + + public: + // Constants + enum types { + BROADCAST = 0x00, + TRANSPORT = 0x01, + RELAY = 0x02, + TUNNEL = 0x03, + NONE = 0xFF, + }; + + enum reachabilities { + REACHABILITY_UNREACHABLE = 0x00, + REACHABILITY_DIRECT = 0x01, + REACHABILITY_TRANSPORT = 0x02, + }; + + static constexpr const char* APP_NAME = "rnstransport"; + + static const uint8_t PATHFINDER_M = 128; // Max hops + // Maximum amount of hops that Reticulum will transport a packet. + + 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 + 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 + + // 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 + static const uint32_t DESTINATION_TIMEOUT = 60*60*24*7; // Destination table entries are removed if unused for one week + static const uint16_t MAX_RECEIPTS = 1024; // 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 + + public: + Transport(); + ~Transport(); + + public: + static void register_destination(Destination &destination); + static bool outbound(const Packet &packet); + + }; + +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..571f8be --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,456 @@ +//#define NDEBUG + +#include "Reticulum.h" +#include "Identity.h" +#include "Destination.h" +#include "Packet.h" +#include "Bytes.h" + +#ifndef NATIVE +#include +#endif + +#include +#include +#include +#include +#include +//#include + +#include + +// Let's define an app name. We'll use this for all +// destinations we create. Since this basic example +// is part of a range of example utilities, we'll put +// them all within the app namespace "example_utilities" +const char* APP_NAME = "example_utilities"; + +// We initialise two lists of strings to use as app_data +const char* fruits[] = {"Peach", "Quince", "Date", "Tangerine", "Pomelo", "Carambola", "Grape"}; +const char* noble_gases[] = {"Helium", "Neon", "Argon", "Krypton", "Xenon", "Radon", "Oganesson"}; + +void testMap() +{ + const uint8_t prestr[] = "Hello"; + const uint8_t poststr[] = "World"; + + RNS::Bytes prebuf(prestr, 5); + assert(prebuf.size() == 5); + assert(memcmp(prebuf.data(), "Hello", prebuf.size()) == 0); + + RNS::Bytes postbuf(poststr, 5); + assert(postbuf.size() == 5); + assert(memcmp(postbuf.data(), "World", postbuf.size()) == 0); + + std::map map; + map.insert({prebuf, "hello"}); + map.insert({postbuf, "world"}); + assert(map.size() == 2); + + auto preit = map.find(prebuf); + assert(preit != map.end()); + assert((*preit).second.compare("hello") == 0); + //if (preit != map.end()) { + // RNS::log(std::string("found prebuf: ") + (*preit).second); + //} + + auto postit = map.find(postbuf); + assert(postit != map.end()); + assert((*postit).second.compare("world") == 0); + //if (postit != map.end()) { + // RNS::log(std::string("found postbuf: ") + (*postit).second); + //} + + const uint8_t newstr[] = "World"; + RNS::Bytes newbuf(newstr, 5); + assert(newbuf.size() == 5); + assert(memcmp(newbuf.data(), "World", newbuf.size()) == 0); + auto newit = map.find(newbuf); + assert(newit != map.end()); + assert((*newit).second.compare("world") == 0); + //if (newit != map.end()) { + // RNS::log(std::string("found newbuf: ") + (*newit).second); + //} + + std::string str = map["World"]; + assert(str.size() == 5); + assert(str.compare("world") == 0); +} + +void testBytes() { + + RNS::Bytes bytes; + assert(!bytes); + assert(bytes.size() == 0); + assert(bytes.empty() == true); + assert(bytes.data() == nullptr); + + const uint8_t prestr[] = "Hello"; + const uint8_t poststr[] = " World"; + + RNS::Bytes prebuf(prestr, 5); + assert(prebuf); + assert(prebuf.size() == 5); + assert(memcmp(prebuf.data(), "Hello", prebuf.size()) == 0); + assert(!(bytes == prebuf)); + assert(bytes != prebuf); + assert(bytes < prebuf); + + RNS::Bytes postbuf(poststr, 6); + assert(postbuf); + assert(postbuf.size() == 6); + assert(memcmp(postbuf.data(), " World", postbuf.size()) == 0); + assert(!(postbuf == bytes)); + assert(postbuf != bytes); + assert(postbuf > bytes); + + assert(!(prebuf == postbuf)); + assert(prebuf != postbuf); + + //if (prebuf == postbuf) { + // RNS::log("bytess are the same"); + //} + //else { + // RNS::log("bytess are different"); + //} + + bytes += prebuf + postbuf; + assert(bytes.size() == 11); + assert(memcmp(bytes.data(), "Hello World", bytes.size()) == 0); + //RNS::log("assign bytes: " + bytes.toString()); + //RNS::log("assign prebuf: " + prebuf.toString()); + //RNS::log("assign postbuf: " + postbuf.toString()); + + bytes = "Foo"; + assert(bytes.size() == 3); + assert(memcmp(bytes.data(), "Foo", bytes.size()) == 0); + + bytes = prebuf + postbuf; + assert(bytes.size() == 11); + assert(memcmp(bytes.data(), "Hello World", bytes.size()) == 0); + + // stream into empty bytes + { + RNS::Bytes strmbuf; + strmbuf << prebuf << postbuf; + //RNS::extreme("stream strmbuf: " + strmbuf.toString()); + //RNS::extreme("stream prebuf: " + prebuf.toString()); + //RNS::extreme("stream postbuf: " + postbuf.toString()); + assert(strmbuf.size() == 11); + assert(memcmp(strmbuf.data(), "Hello World", strmbuf.size()) == 0); + assert(prebuf.size() == 5); + assert(memcmp(prebuf.data(), "Hello", prebuf.size()) == 0); + assert(postbuf.size() == 6); + assert(memcmp(postbuf.data(), " World", postbuf.size()) == 0); + } + + // stream into populated bytes + { + RNS::Bytes strmbuf("Stream "); + assert(strmbuf); + assert(strmbuf.size() == 7); + assert(memcmp(strmbuf.data(), "Stream ", strmbuf.size()) == 0); + + strmbuf << prebuf << postbuf; + //RNS::extreme("stream strmbuf: " + strmbuf.toString()); + //RNS::extreme("stream prebuf: " + prebuf.toString()); + //RNS::extreme("stream postbuf: " + postbuf.toString()); + assert(strmbuf.size() == 18); + assert(memcmp(strmbuf.data(), "Stream Hello World", strmbuf.size()) == 0); + assert(prebuf.size() == 5); + assert(memcmp(prebuf.data(), "Hello", prebuf.size()) == 0); + assert(postbuf.size() == 6); + assert(memcmp(postbuf.data(), " World", postbuf.size()) == 0); + } + + // stream with assignment + // (this is a known and correct but perhaps unexpected and non-intuitive side-effect of assignment with stream) + { + RNS::Bytes strmbuf = prebuf << postbuf; + //RNS::extreme("stream strmbuf: " + strmbuf.toString()); + //RNS::extreme("stream prebuf: " + prebuf.toString()); + //RNS::extreme("stream postbuf: " + postbuf.toString()); + assert(strmbuf.size() == 11); + assert(memcmp(strmbuf.data(), "Hello World", strmbuf.size()) == 0); + assert(prebuf.size() == 11); + assert(memcmp(prebuf.data(), "Hello World", prebuf.size()) == 0); + assert(postbuf.size() == 6); + assert(memcmp(postbuf.data(), " World", postbuf.size()) == 0); + } + + { + RNS::Bytes nonebuf = nullptr; + assert(!nonebuf); + assert(nonebuf.size() == 0); + assert(nonebuf.data() == nullptr); + } + +} + +void testCowBytes() { + + RNS::Bytes bytes1("1"); + assert(bytes1.size() == 1); + assert(memcmp(bytes1.data(), "1", bytes1.size()) == 0); + + RNS::Bytes bytes2(bytes1); + assert(bytes2.size() == 1); + assert(memcmp(bytes2.data(), "1", bytes2.size()) == 0); + assert(bytes2.data() == bytes1.data()); + + RNS::Bytes bytes3(bytes2); + assert(bytes3.size() == 1); + assert(memcmp(bytes3.data(), "1", bytes3.size()) == 0); + assert(bytes3.data() == bytes2.data()); + + //RNS::log("pre bytes1 ptr: " + std::to_string((uint32_t)bytes1.data()) + " data: " + bytes1.toString()); + //RNS::log("pre bytes2 ptr: " + std::to_string((uint32_t)bytes2.data()) + " data: " + bytes2.toString()); + //RNS::log("pre bytes3 ptr: " + std::to_string((uint32_t)bytes3.data()) + " data: " + bytes3.toString()); + + //bytes1.append("mississippi"); + //assert(bytes1.size() == 12); + //assert(memcmp(bytes1.data(), "1mississippi", bytes1.size()) == 0); + //assert(bytes1.data() != bytes2.data()); + + bytes2.append("mississippi"); + assert(bytes2.size() == 12); + assert(memcmp(bytes2.data(), "1mississippi", bytes2.size()) == 0); + assert(bytes2.data() != bytes1.data()); + + bytes3.assign("mississippi"); + assert(bytes3.size() == 11); + assert(memcmp(bytes3.data(), "mississippi", bytes3.size()) == 0); + assert(bytes3.data() != bytes2.data()); + + //RNS::log("post bytes1 ptr: " + std::to_string((uint32_t)bytes1.data()) + " data: " + bytes1.toString()); + //RNS::log("post bytes2 ptr: " + std::to_string((uint32_t)bytes2.data()) + " data: " + bytes2.toString()); + //RNS::log("post bytes3 ptr: " + std::to_string((uint32_t)bytes3.data()) + " data: " + bytes3.toString()); +} + +void testObjects() { + + RNS::Reticulum reticulum_default; + assert(reticulum_default); + + RNS::Reticulum reticulum_none(RNS::Reticulum::NONE); + assert(!reticulum_none); + + RNS::Reticulum reticulum_default_copy(reticulum_default); + assert(reticulum_default_copy); + + RNS::Reticulum reticulum_none_copy(reticulum_none); + assert(!reticulum_none_copy); + +} + +void testBytesConversion() { + + { + RNS::Bytes bytes("Hello World"); + std::string hex = bytes.toHex(); + RNS::extreme("text: \"" + bytes.toString() + "\" upper hex: \"" + hex + "\""); + assert(hex.length() == 22); + assert(hex.compare("48656C6C6F20576F726C64") == 0); + } + { + RNS::Bytes bytes("Hello World"); + std::string hex = bytes.toHex(false); + RNS::extreme("text: \"" + bytes.toString() + "\" lower hex: \"" + hex + "\""); + assert(hex.length() == 22); + assert(hex.compare("48656c6c6f20576f726c64") == 0); + } + { + std::string hex("48656C6C6F20576F726C64"); + RNS::Bytes bytes; + bytes.assignHex(hex.c_str()); + std::string text = bytes.toString(); + RNS::extreme("hex: \"" + hex + "\" text: \"" + text + "\""); + assert(text.length() == 11); + assert(text.compare("Hello World") == 0); + } + { + std::string hex("48656c6c6f20576f726c64"); + RNS::Bytes bytes; + bytes.assignHex(hex.c_str()); + std::string text = bytes.toString(); + RNS::extreme("hex: \"" + hex + "\" text: \"" + text + "\""); + assert(text.length() == 11); + assert(text.compare("Hello World") == 0); + + bytes.assignHex(hex.c_str()); + text = bytes.toString(); + RNS::extreme("hex: \"" + hex + "\" text: \"" + text + "\""); + assert(text.length() == 11); + assert(text.compare("Hello World") == 0); + + bytes.appendHex(hex.c_str()); + text = bytes.toString(); + RNS::extreme("hex: \"" + hex + "\" text: \"" + text + "\""); + assert(text.length() == 22); + assert(text.compare("Hello WorldHello World") == 0); + } + +} + + +/* +void announceLoop(RNS::Destination &destination_1, RNS::Destination &destination_2) { + // Let the user know that everything is ready + RNS::log("Announce example running, hit enter to manually send an announce (Ctrl-C to quit)"); + + // We enter a loop that runs until the users exits. + // If the user hits enter, we will announce our server + // destination on the network, which will let clients + // know how to create messages directed towards it. + //while (true) { + //zentered = input(); + RNS::log("Sending announce..."); + + // Randomly select a fruit + //const char* fruit = fruits[rand() % sizeof(fruits)]; + const char* fruit = fruits[rand() % 7]; + //RNS::log(fruit); + RNS::log(std::string("fruit: ") + fruit); + + // Send the announce including the app data + if (destination_1) { + //destination_1.announce(RNS::bytesFromString(fruit)); + // CBA TEST path + destination_1.announce(RNS::bytesFromString(fruit), true, nullptr, RNS::bytesFromString("test_tag")); + //zRNS::log(std::string("Sent announce from ") + RNS::prettyhexrep(destination_1->_hash) +" ("+ (const char*)destination_1->_name + ")"); + } + + // Randomly select a noble gas + //const char* noble_gas = noble_gases[rand() % sizeof(noble_gas)]; + const char* noble_gas = noble_gases[rand() % 7]; + //RNS::log(noble_gas); + RNS::log(std::string("noble_gas: ") + noble_gas); + + // Send the announce including the app data + if (destination_2) { + destination_2.announce(RNS::bytesFromString(noble_gas)); + //zRNS::log(std::string("Sent announce from ") + RNS::prettyhexrep(destination_2->_hash) + " (" + destination_2->_name + ")"); + } + +#ifndef NATIVE + delay(1000); +#else + usleep(1000000); +#endif + //} +} + +// This initialisation is executed when the program is started +void program_setup() { + + // We must first initialise Reticulum + RNS::Reticulum reticulum; + + // Randomly create a new identity for our example + RNS::Identity identity; + + // Using the identity we just created, we create two destinations + // in the "example_utilities.announcesample" application space. + // + // Destinations are endpoints in Reticulum, that can be addressed + // and communicated with. Destinations can also announce their + // existence, which will let the network know they are reachable + // and autoomatically create paths to them, from anywhere else + // in the network. + RNS::Destination destination_1(identity, RNS::Destination::IN, RNS::Destination::SINGLE, APP_NAME, "announcesample.fruits"); + // CBA TEST no identity + //RNS::Destination destination_1(RNS::Identity::NONE, RNS::Destination::IN, RNS::Destination::SINGLE, APP_NAME, "announcesample.fruits"); + + //RNS::Destination *destination_2(identity, RNS::Destination::IN, RNS::Destination::SINGLE, APP_NAME, "announcesample.noble_gases"); + RNS::Destination destination_2(RNS::Destination::NONE); + + // We configure the destinations to automatically prove all + // packets adressed to it. By doing this, RNS will automatically + // generate a proof for each incoming packet and transmit it + // back to the sender of that packet. This will let anyone that + // tries to communicate with the destination know whether their + // communication was received correctly. + //zdestination_1->set_proof_strategy(RNS::Destination::PROVE_ALL); + //zdestination_2->set_proof_strategy(RNS::Destination::PROVE_ALL); + + // We create an announce handler and configure it to only ask for + // announces from "example_utilities.announcesample.fruits". + // Try changing the filter and see what happens. + //zannounce_handler = ExampleAnnounceHandler( + //z aspect_filter="example_utilities.announcesample.fruits"; + //z) + + // We register the announce handler with Reticulum + //zRNS::Transport.register_announce_handler(announce_handler); + + // Everything's ready! + // Let's hand over control to the announce loop + announceLoop(destination_1, destination_2); +} +*/ + +#ifndef NATIVE + +void setup() { + + Serial.begin(115200); + Serial.print("Hello from T-Beam on PlatformIO!\n"); + + //std::stringstream test; + // !!! just adding this single stringstream alone (not even using it) adds a whopping 17.1% !!! + // !!! JUST SAY NO TO STRINGSTREAM !!! + + RNS::loglevel(RNS::LOG_EXTREME); + + // 18.5% completely empty program + + // 21.8% baseline here with serial + + RNS::Reticulum reticulum; + // 21.9% (+0.1%) + + RNS::Identity identity; + // 22.6% (+0.7%) + + RNS::Destination destination(identity, RNS::Destination::IN, RNS::Destination::SINGLE, "test", "context"); + // 23.0% (+0.4%) + + destination.announce(RNS::bytesFromString("fruit"), true, nullptr, RNS::bytesFromString("test_tag")); + // 23.9% (+0.8%) + +#ifndef NDEBUG + // begin with logging turned down for unit tests + RNS::loglevel(RNS::LOG_WARNING); + //RNS::loglevel(RNS::LOG_EXTREME); + + testMap(); + testBytes(); + testCowBytes(); + testObjects(); + testBytesConversion(); + + // 24.8% (+0.9%) +#endif + + // increase logging for functional tests + RNS::loglevel(RNS::LOG_EXTREME); + + //program_setup(); + + Serial.print("Goodbye from T-Beam on PlatformIO!\n"); +} + +void loop() { +} + +#else + +int main(int argc, char **argv) { + printf("Hello from Native on PlatformIO!\n"); + + program_setup(); + + printf("Goodbye from Native on PlatformIO!\n"); +} + +#endif diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..7380eaa --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,157 @@ +#include "Reticulum.h" +#include "Identity.h" +#include "Destination.h" + +#ifndef NATIVE +#include +#endif +#include +#include + +#include + +// Let's define an app name. We'll use this for all +// destinations we create. Since this basic example +// is part of a range of example utilities, we'll put +// them all within the app namespace "example_utilities" +const char* APP_NAME = "example_utilities"; + +// We initialise two lists of strings to use as app_data +const char* fruits[] = {"Peach", "Quince", "Date", "Tangerine", "Pomelo", "Carambola", "Grape"}; +const char* noble_gases[] = {"Helium", "Neon", "Argon", "Krypton", "Xenon", "Radon", "Oganesson"}; + +void announceLoop(RNS::Destination* destination_1, RNS::Destination* destination_2) { + // Let the user know that everything is ready + RNS::log("Announce example running, hit enter to manually send an announce (Ctrl-C to quit)"); + + // We enter a loop that runs until the users exits. + // If the user hits enter, we will announce our server + // destination on the network, which will let clients + // know how to create messages directed towards it. + while (true) { + //zentered = input(); +#ifndef NATIVE + delay(1000); +#else + usleep(1000); +#endif + RNS::log("Sending announce..."); + + // Randomly select a fruit + //const char* fruit = fruits[rand() % sizeof(fruits)]; + const char* fruit = fruits[rand() % 7]; + RNS::log(fruit); + //RNS::log(String("fruit: ") + fruit); + + // Send the announce including the app data +/* + destination_1->announce(app_data=fruit.encode("utf-8")); + RNS::log( + "Sent announce from "+ + RNS.prettyhexrep(destination_1.hash)+ + " ("+destination_1.name+")" + ); +*/ + + // Randomly select a noble gas + //const char* noble_gas = noble_gases[rand() % sizeof(noble_gas)]; + const char* noble_gas = noble_gases[rand() % 7]; + RNS::log(noble_gas); + //RNS::log(String("noble_gas: ") + noble_gas); + + // Send the announce including the app data +/* + destination_2->announce(app_data=noble_gas.encode("utf-8")); + RNS::log( + "Sent announce from "+ + RNS.prettyhexrep(destination_2.hash)+ + " ("+destination_2.name+")" + ); +*/ + } +} + +// This initialisation is executed when the program is started +void program_setup() { + // We must first initialise Reticulum + RNS::Reticulum* reticulum = new RNS::Reticulum(); + + // Randomly create a new identity for our example + RNS::Identity* identity = new RNS::Identity(); + + // Using the identity we just created, we create two destinations + // in the "example_utilities.announcesample" application space. + // + // Destinations are endpoints in Reticulum, that can be addressed + // and communicated with. Destinations can also announce their + // existence, which will let the network know they are reachable + // and autoomatically create paths to them, from anywhere else + // in the network. + RNS::Destination* destination_1 = new RNS::Destination( + identity, + RNS::Destination::IN, + RNS::Destination::SINGLE, + APP_NAME, + //z"announcesample", + //z"fruits" + nullptr + ); + + RNS::Destination* destination_2 = new RNS::Destination( + identity, + RNS::Destination::IN, + RNS::Destination::SINGLE, + APP_NAME, + //z"announcesample", + //z"noble_gases" + nullptr + ); + + // We configure the destinations to automatically prove all + // packets adressed to it. By doing this, RNS will automatically + // generate a proof for each incoming packet and transmit it + // back to the sender of that packet. This will let anyone that + // tries to communicate with the destination know whether their + // communication was received correctly. + //zdestination_1->set_proof_strategy(RNS::Destination::PROVE_ALL); + //zdestination_2->set_proof_strategy(RNS::Destination::PROVE_ALL); + + // We create an announce handler and configure it to only ask for + // announces from "example_utilities.announcesample.fruits". + // Try changing the filter and see what happens. + //zannounce_handler = ExampleAnnounceHandler( + //z aspect_filter="example_utilities.announcesample.fruits"; + //z) + + // We register the announce handler with Reticulum + //zRNS::Transport.register_announce_handler(announce_handler); + + // Everything's ready! + // Let's hand over control to the announce loop + announceLoop(destination_1, destination_2); +} + + +#ifndef NATIVE + +void setup() { + + Serial.begin(115200); + RNS::log("Hello T-Beam from PlatformIO!"); + + program_setup(); +} + +void loop() { +} + +#else + +int main(int argc, char **argv) { + RNS::log("Hello Native from PlatformIO!"); + UNITY_BEGIN(); + RUN_TEST(program_setup); + UNITY_END(); +} + +#endif