diff --git a/platformio.ini b/platformio.ini index 5c932f9..a9a2be3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,16 +11,10 @@ [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 + -std=c++11 -Wall -Wextra -Wno-missing-field-initializers @@ -28,7 +22,7 @@ build_flags = -Isrc -DNATIVE lib_deps = -; rweather/Crypto@^0.4.0 + rweather/Crypto@^0.4.0 lib_compat_mode = off [env:ttgo-t-beam] diff --git a/src/Bytes.h b/src/Bytes.h index eb5bb7b..c963c3f 100644 --- a/src/Bytes.h +++ b/src/Bytes.h @@ -29,30 +29,30 @@ namespace RNS { public: Bytes() { - //extreme("Bytes object created from default, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + //extreme("Bytes object created from default, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_data.get())); } Bytes(NoneConstructor none) { - //extreme("Bytes object created from NONE, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + //extreme("Bytes object created from NONE, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_data.get())); } Bytes(const Bytes &bytes) { //extreme("Bytes is using shared data"); assign(bytes); - //extreme("Bytes object copy created from bytes \"" + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + //extreme("Bytes object copy created from bytes \"" + toString() + "\", this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_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())); + //extreme(std::string("Bytes object created from chunk \"") + toString() + "\", this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_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())); + //extreme(std::string("Bytes object created from string \"") + toString() + "\", this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_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())); + //extreme(std::string("Bytes object created from std::string \"") + toString() + "\", this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_data.get())); } - ~Bytes() { - //extreme(std::string("Bytes object destroyed \"") + toString() + "\", this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_data.get())); + virtual ~Bytes() { + //extreme(std::string("Bytes object destroyed \"") + toString() + "\", this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((unsigned long)_data.get())); } inline Bytes& operator = (const Bytes &bytes) { @@ -206,6 +206,28 @@ namespace RNS { inline Bytes left(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data(), len}; } inline Bytes right(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data() + (size() - len), len}; } + // Python array indexing + // [8:16] + // pos 8 to pos 16 + // mid(8, 8) + // [:16] + // start to pos 16 (same as first 16) + // left(16) + // [16:] + // pos 16 to end + // mid(16) + // [-16:] + // last 16 + // right(16) + // [:-16] + // all except the last 16 + // left(size()-16) + // mid(0, size()-16) + // [-1] + // last element + // [-2] + // second to last element + private: SharedData _data; mutable bool _owner = true; diff --git a/src/Cryptography/Fernet.cpp b/src/Cryptography/Fernet.cpp index 1f64c85..13e755c 100644 --- a/src/Cryptography/Fernet.cpp +++ b/src/Cryptography/Fernet.cpp @@ -21,28 +21,6 @@ Fernet::Fernet(const Bytes &key) { throw std::invalid_argument("Fernet key must be 32 bytes, not " + std::to_string(key.size())); } - // Python array indexing - // [8:16] - // pos 8 to pos 16 - // mid(8, 8) - // [:16] - // start to pos 16 (same as first 16) - // left(16) - // [16:] - // pos 16 to end - // mid(16) - // [-16:] - // last 16 - // right(16) - // [:-16] - // all except the last 16 - // left(size()-16) - // mid(0, size()-16) - // [-1] - // last element - // [-2] - // seocnd to last element - //self._signing_key = key[:16] _signing_key = key.left(16); //self._encryption_key = key[16:] diff --git a/src/Cryptography/PKCS7.h b/src/Cryptography/PKCS7.h index a03f7ee..c508406 100644 --- a/src/Cryptography/PKCS7.h +++ b/src/Cryptography/PKCS7.h @@ -31,11 +31,15 @@ namespace RNS { namespace Cryptography { //debug("PKCS7::pad: len: " + std::to_string(len)); size_t padlen = bs - (len % bs); //debug("PKCS7::pad: pad len: " + std::to_string(padlen)); - // create byte array of size n? - //v = bytes([padlen]) - uint8_t pad[padlen] = {0}; + // create zero-filled byte padding array of size padlen + //p v = bytes([padlen]) + //uint8_t pad[padlen] = {0}; + uint8_t pad[padlen]; + memset(pad, 0, padlen); + // set last byte of padding array to size of padding pad[padlen-1] = (uint8_t)padlen; - //return data+v*padlen + // concatenate data with padding + //p return data+v*padlen data.append(pad, padlen); //debug("PKCS7::pad: data size: " + std::to_string(data.size())); } @@ -44,13 +48,14 @@ namespace RNS { namespace Cryptography { static inline void inplace_unpad(Bytes &data, size_t bs = BLOCKSIZE) { size_t len = data.size(); //debug("PKCS7::unpad: len: " + std::to_string(len)); - // last byte is pad length + // read last byte which is pad length //pad = data[-1] size_t padlen = (size_t)data.data()[data.size()-1]; //debug("PKCS7::unpad: pad len: " + std::to_string(padlen)); if (padlen > bs) { throw std::runtime_error("Cannot unpad, invalid padding length of " + std::to_string(padlen) + " bytes"); } + // truncate data to strip padding //return data[:len-padlen] data.resize(len - padlen); //debug("PKCS7::unpad: data size: " + std::to_string(data.size())); diff --git a/src/Cryptography/Random.h b/src/Cryptography/Random.h index 1d64fc7..3a4b955 100644 --- a/src/Cryptography/Random.h +++ b/src/Cryptography/Random.h @@ -7,10 +7,32 @@ namespace RNS { namespace Cryptography { + // return vector specified length of random bytes inline Bytes random(size_t length) { Bytes rand; RNG.rand(rand.writable(length), length); return rand; } + // return 32 bit random unigned int + inline uint32_t randomnum() { + Bytes rand; + RNG.rand(rand.writable(4), 4); + uint32_t randnum = uint32_t((unsigned char)(rand.data()[0]) << 24 | + (unsigned char)(rand.data()[0]) << 16 | + (unsigned char)(rand.data()[0]) << 8 | + (unsigned char)(rand.data()[0])); + return randnum; + } + + // return 32 bit random unigned int between 0 and specified value + inline uint32_t randomnum(uint32_t max) { + return randomnum() % max; + } + + // return random float value from 0 to 1 + inline float random() { + return (float)(randomnum() / (float)0xffffffff); + } + } } diff --git a/src/Cryptography/X25519.h b/src/Cryptography/X25519.h index 92cebf1..488b661 100644 --- a/src/Cryptography/X25519.h +++ b/src/Cryptography/X25519.h @@ -55,8 +55,8 @@ namespace RNS { namespace Cryptography { class X25519PrivateKey { public: - const float MIN_EXEC_TIME = 0.002; - const float MAX_EXEC_TIME = 0.5; + const float MIN_EXEC_TIME = 2; // in milliseconds + const float MAX_EXEC_TIME = 500; // in milliseconds const uint8_t DELAY_WINDOW = 10; //zT_CLEAR = None @@ -134,11 +134,11 @@ namespace RNS { namespace Cryptography { if isinstance(peer_public_key, bytes): peer_public_key = X25519PublicKey.from_public_bytes(peer_public_key) - start = time.time() + start = OS::time() shared = _pack_number(_raw_curve25519(peer_public_key.x, _a)) - end = time.time() + end = OS::time() duration = end-start if X25519PrivateKey.T_CLEAR == None: @@ -158,7 +158,7 @@ namespace RNS { namespace Cryptography { target = start+X25519PrivateKey.MIN_EXEC_TIME try: - time.sleep(target-time.time()) + OS::sleep(target-OS::time()) except Exception as e: pass diff --git a/src/Destination.cpp b/src/Destination.cpp index 4a871db..ac6c893 100644 --- a/src/Destination.cpp +++ b/src/Destination.cpp @@ -54,10 +54,6 @@ Destination::Destination(const Identity &identity, const directions direction, c 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. */ @@ -124,7 +120,7 @@ Packet Destination::announce(const Bytes &app_data, bool path_response, Interfac // vector //Response &entry = *it; // map - Response &entry = (*it).second; + PathResponse &entry = (*it).second; if (now > (entry.first + Destination::PR_TAG_WINDOW)) { it = _object->_path_responses.erase(it); } @@ -218,6 +214,7 @@ Packet Destination::announce(const Bytes &app_data, bool path_response, Interfac Packet announce_packet(*this, announce_data, Packet::ANNOUNCE, announce_context, Transport::BROADCAST, Packet::HEADER_1, nullptr, attached_interface); if (send) { + debug("Destination::announce: sending announce packet..."); announce_packet.send(); return Packet::NONE; } diff --git a/src/Destination.h b/src/Destination.h index aea8e34..21bf012 100644 --- a/src/Destination.h +++ b/src/Destination.h @@ -1,15 +1,16 @@ #pragma once #include "Reticulum.h" +#include "Link.h" #include "Identity.h" #include "Bytes.h" +#include "None.h" #include #include #include #include #include -#include #include namespace RNS { @@ -17,6 +18,7 @@ namespace RNS { class Interface; class Packet; class Link; + class Identity; /** * @brief A class used to describe endpoints in a Reticulum Network. Destination @@ -40,7 +42,7 @@ namespace RNS { using link_established = void(*)(const Link &link); //using packet = void(*)(uint8_t *data, uint16_t data_len, Packet *packet); using packet = void(*)(const Bytes &data, const Packet &packet); - using proof_requested = void(*)(const Packet &packet); + using proof_requested = bool(*)(const Packet &packet); public: link_established _link_established = nullptr; packet _packet = nullptr; @@ -49,8 +51,8 @@ namespace RNS { }; //typedef std::pair Response; - using Response = std::pair; - //using Response = std::pair>; + using PathResponse = std::pair; + //using PathResponse = std::pair>; enum NoneConstructor { NONE @@ -87,20 +89,28 @@ namespace RNS { Destination(NoneConstructor none) { extreme("Destination NONE object created"); } + Destination(RNS::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(); + virtual ~Destination() { + extreme("Destination object destroyed"); + } 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())); + extreme("Destination object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); return *this; } inline operator bool() const { return _object.get() != nullptr; } + inline bool operator < (const Destination &destination) const { + return _object.get() < destination._object.get(); + } public: static Bytes hash(const Identity &identity, const char *app_name, const char *aspects); @@ -172,12 +182,15 @@ namespace RNS { // 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 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; } + inline Link::status status() const { assert(_object); return _object->_status; } + inline const Callbacks &callbacks() const { assert(_object); return _object->_callbacks; } + inline const Identity &identity() const { assert(_object); return _object->_identity; } inline std::string toString() const { assert(_object); return "{Destination:" + _object->_hash.toHex() + "}"; } @@ -185,6 +198,7 @@ namespace RNS { class Object { public: Object(const Identity &identity) : _identity(identity) {} + virtual ~Object() {} private: bool _accept_link_requests = true; Callbacks _callbacks; @@ -194,8 +208,8 @@ namespace RNS { proof_strategies _proof_strategy = PROVE_NONE; uint16_t _mtu = 0; - //std::vector _path_responses; - std::map _path_responses; + //std::vector _path_responses; + std::map _path_responses; //z_links = [] Identity _identity; @@ -214,6 +228,9 @@ namespace RNS { // 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; + + Link::status _status; + friend class Destination; }; std::shared_ptr _object; diff --git a/src/Identity.cpp b/src/Identity.cpp index f5b017e..b375287 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -16,7 +16,7 @@ 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())); + extreme("Identity object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); } @@ -49,6 +49,81 @@ void Identity::createKeys() { } +/*static*/ bool Identity::validate_announce(const Packet &packet) { +/* + try: + if packet.packet_type == RNS.Packet.ANNOUNCE: + destination_hash = packet.destination_hash + public_key = packet.data[:Identity.KEYSIZE//8] + name_hash = packet.data[Identity.KEYSIZE//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8] + random_hash = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10] + signature = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8] + app_data = b"" + if len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8: + app_data = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:] + + signed_data = destination_hash+public_key+name_hash+random_hash+app_data + + if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8: + app_data = None + + announced_identity = Identity(create_keys=False) + announced_identity.load_public_key(public_key) + + if announced_identity.pub != None and announced_identity.validate(signature, signed_data): + hash_material = name_hash+announced_identity.hash + expected_hash = RNS.Identity.full_hash(hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8] + + if destination_hash == expected_hash: + # Check if we already have a public key for this destination + # and make sure the public key is not different. + if destination_hash in Identity.known_destinations: + if public_key != Identity.known_destinations[destination_hash][2]: + # In reality, this should never occur, but in the odd case + # that someone manages a hash collision, we reject the announce. + RNS.log("Received announce with valid signature and destination hash, but announced public key does not match already known public key.", RNS.LOG_CRITICAL) + RNS.log("This may indicate an attempt to modify network paths, or a random hash collision. The announce was rejected.", RNS.LOG_CRITICAL) + return False + + RNS.Identity.remember(packet.get_hash(), destination_hash, public_key, app_data) + del announced_identity + + if packet.rssi != None or packet.snr != None: + signal_str = " [" + if packet.rssi != None: + signal_str += "RSSI "+str(packet.rssi)+"dBm" + if packet.snr != None: + signal_str += ", " + if packet.snr != None: + signal_str += "SNR "+str(packet.snr)+"dB" + signal_str += "]" + else: + signal_str = "" + + if hasattr(packet, "transport_id") and packet.transport_id != None: + RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received via "+RNS.prettyhexrep(packet.transport_id)+" on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME) + else: + RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME) + + return True + + else: + RNS.log("Received invalid announce for "+RNS.prettyhexrep(destination_hash)+": Destination mismatch.", RNS.LOG_DEBUG) + return False + + else: + RNS.log("Received invalid announce for "+RNS.prettyhexrep(destination_hash)+": Invalid signature.", RNS.LOG_DEBUG) + del announced_identity + return False + + except Exception as e: + RNS.log("Error occurred while validating announce. The contained exception was: "+str(e), RNS.LOG_ERROR) + return False +*/ + // MOCK + return true; +} + /* Encrypts information for the identity. diff --git a/src/Identity.h b/src/Identity.h index f61f570..d55e757 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -5,6 +5,7 @@ //#include "Destination.h" #include "Log.h" #include "Bytes.h" +#include "None.h" #include "Cryptography/Hashes.h" #include "Cryptography/Ed25519.h" #include "Cryptography/X25519.h" @@ -46,24 +47,30 @@ namespace RNS { public: Identity(NoneConstructor none) { - extreme("Identity NONE object created, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_object.get())); + extreme("Identity NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); + } + Identity(RNS::NoneConstructor none) { + extreme("Identity NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); } Identity(const Identity &identity) : _object(identity._object) { - extreme("Identity object copy created, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((ulong)_object.get())); + extreme("Identity object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_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())); + virtual ~Identity() { + extreme("Identity object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); } inline Identity& operator = (const Identity &identity) { _object = identity._object; - extreme("Identity object copy created by assignment, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((uint32_t)_object.get())); + extreme("Identity object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); return *this; } inline operator bool() const { return _object.get() != nullptr; } + inline bool operator < (const Identity &identity) const { + return _object.get() < identity._object.get(); + } public: void createKeys(); @@ -110,6 +117,8 @@ namespace RNS { return truncated_hash(Cryptography::random(Identity::TRUNCATED_HASHLENGTH/8)); } + static bool validate_announce(const Packet &packet); + inline Bytes get_salt() { assert(_object); return _object->_hash; } inline Bytes get_context() { return Bytes::NONE; } @@ -133,8 +142,8 @@ namespace RNS { 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)); } + Object() { extreme("Identity::Data object created, this: " + std::to_string((uintptr_t)this)); } + virtual ~Object() { extreme("Identity::Data object destroyed, this: " + std::to_string((uintptr_t)this)); } private: RNS::Cryptography::X25519PrivateKey::Ptr _prv; diff --git a/src/Interfaces/Interface.cpp b/src/Interfaces/Interface.cpp index 2383979..030e667 100644 --- a/src/Interfaces/Interface.cpp +++ b/src/Interfaces/Interface.cpp @@ -1,14 +1,65 @@ #include "Interface.h" -#include "Log.h" +#include "../Transport.h" using namespace RNS; -Interface::Interface() { - extreme("Interface object created"); + +/*virtual*/ inline void Interface::processIncoming(const Bytes &data) { + extreme("Interface::processIncoming: data: " + data.toHex()); + assert(_object); + _object->_rxb += data.size(); + // CBA TODO implement concept of owner or a callback mechanism for incoming data + //_object->_owner.inbound(data, *this); + Transport::inbound(data, *this); } -Interface::~Interface() { - extreme("Interface object destroyed"); +/*virtual*/ inline void Interface::processOutgoing(const Bytes &data) { + extreme("Interface::processOutgoing: data: " + data.toHex()); + assert(_object); + _object->_txb += data.size(); } +void Interface::process_announce_queue() { +/* + if not hasattr(self, "announce_cap"): + self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP + + if hasattr(self, "announce_queue"): + try: + now = time.time() + stale = [] + for a in self.announce_queue: + if now > a["time"]+RNS.Reticulum.QUEUED_ANNOUNCE_LIFE: + stale.append(a) + + for s in stale: + if s in self.announce_queue: + self.announce_queue.remove(s) + + if len(self.announce_queue) > 0: + min_hops = min(entry["hops"] for entry in self.announce_queue) + entries = list(filter(lambda e: e["hops"] == min_hops, self.announce_queue)) + entries.sort(key=lambda e: e["time"]) + selected = entries[0] + + now = time.time() + tx_time = (len(selected["raw"])*8) / self.bitrate + wait_time = (tx_time / self.announce_cap) + self.announce_allowed_at = now + wait_time + + self.processOutgoing(selected["raw"]) + + if selected in self.announce_queue: + self.announce_queue.remove(selected) + + if len(self.announce_queue) > 0: + timer = threading.Timer(wait_time, self.process_announce_queue) + timer.start() + + except Exception as e: + self.announce_queue = [] + RNS.log("Error while processing announce queue on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) + RNS.log("The announce queue for this interface has been cleared.", RNS.LOG_ERROR) +*/ +} diff --git a/src/Interfaces/Interface.h b/src/Interfaces/Interface.h index 3a88383..2a8e6a2 100644 --- a/src/Interfaces/Interface.h +++ b/src/Interfaces/Interface.h @@ -1,13 +1,35 @@ #pragma once #include "../Log.h" +#include "../Bytes.h" +#include "../None.h" +#include #include +#include namespace RNS { class Interface { + public: + class AnnounceEntry { + public: + AnnounceEntry() {} + AnnounceEntry(const Bytes &destination, uint64_t time, uint8_t hops, uint64_t emitted, const Bytes &raw) : + _destination(destination), + _time(time), + _hops(hops), + _emitted(emitted), + _raw(raw) {} + public: + Bytes _destination; + uint64_t _time = 0; + uint8_t _hops = 0; + uint64_t _emitted = 0; + Bytes _raw; + }; + public: enum NoneConstructor { NONE @@ -16,42 +38,106 @@ namespace RNS { 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, + MODE_NONE = 0x00, + MODE_FULL = 0x01, + MODE_POINT_TO_POINT = 0x04, + MODE_ACCESS_POINT = 0x08, + MODE_ROAMING = 0x10, + MODE_BOUNDARY = 0x20, + MODE_GATEWAY = 0x40, }; // Which interface modes a Transport Node // should actively discover paths for. - //zDISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY] + uint8_t DISCOVER_PATHS_FOR = MODE_ACCESS_POINT | MODE_GATEWAY; - public: + public: Interface(NoneConstructor none) { extreme("Interface object NONE created"); } + Interface(RNS::NoneConstructor none) { + extreme("Interface object NONE created"); + } Interface(const Interface &interface) : _object(interface._object) { extreme("Interface object copy created"); } - Interface(); - ~Interface(); + Interface() : _object(new Object()) { + extreme("Interface object created"); + } + Interface(const char *name) : _object(new Object(name)) { + extreme("Interface object created"); + } + virtual ~Interface() { + extreme("Interface object destroyed"); + } inline Interface& operator = (const Interface &interface) { _object = interface._object; - extreme("Interface object copy created by assignment, this: " + std::to_string((ulong)this) + ", data: " + std::to_string((uint32_t)_object.get())); + extreme("Interface object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); return *this; } inline operator bool() const { return _object.get() != nullptr; } + inline bool operator < (const Interface &interface) const { + return _object.get() < interface._object.get(); + } + + public: + inline Bytes get_hash() const { /*return Identity::full_hash();*/ return {}; } + void process_announce_queue(); + inline void detach() {} + + virtual void processIncoming(const Bytes &data); + virtual void processOutgoing(const Bytes &data); + + inline void add_announce(AnnounceEntry &entry) { assert(_object); _object->_announce_queue.push_back(entry); } + + // getters/setters + protected: + inline void IN(bool IN) { assert(_object); _object->_IN = IN; } + inline void OUT(bool OUT) { assert(_object); _object->_OUT = OUT; } + inline void FWD(bool FWD) { assert(_object); _object->_FWD = FWD; } + inline void RPT(bool RPT) { assert(_object); _object->_RPT = RPT; } + inline void name(const char *name) { assert(_object); _object->_name = name; } + public: + inline bool IN() const { assert(_object); return _object->_IN; } + inline bool OUT() const { assert(_object); return _object->_OUT; } + inline bool FWD() const { assert(_object); return _object->_FWD; } + inline bool RPT() const { assert(_object); return _object->_RPT; } + inline std::string name() const { assert(_object); return _object->_name; } + inline Bytes ifac_identity() const { assert(_object); return _object->_ifac_identity; } + inline modes mode() const { assert(_object); return _object->_mode; } + inline uint32_t bitrate() const { assert(_object); return _object->_bitrate; } + inline uint64_t announce_allowed_at() const { assert(_object); return _object->_announce_allowed_at; } + inline void announce_allowed_at(uint64_t announce_allowed_at) { assert(_object); _object->_announce_allowed_at = announce_allowed_at; } + inline float announce_cap() const { assert(_object); return _object->_announce_cap; } + inline std::list announce_queue() const { assert(_object); return _object->_announce_queue; } + + virtual inline std::string toString() const { assert(_object); return "Interface[" + _object->_name + "]"; } private: class Object { + public: + Object() {} + Object(const char *name) : _name(name) {} + virtual ~Object() {} private: - - + bool _IN = false; + bool _OUT = false; + bool _FWD = false; + bool _RPT = false; + std::string _name; + size_t _rxb = 0; + size_t _txb = 0; + bool online = false; + Bytes _ifac_identity; + modes _mode = MODE_NONE; + uint32_t _bitrate = 0; + uint64_t _announce_allowed_at = 0; + float _announce_cap = 0.0; + std::list _announce_queue; + //Transport &_owner; friend class Interface; }; std::shared_ptr _object; diff --git a/src/Link.cpp b/src/Link.cpp index 1917f67..6c4fe3b 100644 --- a/src/Link.cpp +++ b/src/Link.cpp @@ -1,14 +1,22 @@ #include "Link.h" +#include "Packet.h" #include "Log.h" using namespace RNS; -Link::Link() { - log("Link object created", LOG_EXTREME); +Link::Link() : _object(new Object()) { + assert(_object); + + extreme("Link object created"); } -Link::~Link() { - log("Link object destroyed", LOG_EXTREME); + +void Link::set_link_id(const Packet &packet) { + assert(_object); + _object->_link_id = packet.getTruncatedHash(); + _object->_hash = _object->_link_id; } +void Link::receive(const Packet &packet) { +} diff --git a/src/Link.h b/src/Link.h index f2741f6..59da8d3 100644 --- a/src/Link.h +++ b/src/Link.h @@ -2,6 +2,12 @@ #include "Reticulum.h" #include "Identity.h" +// CBA TODO resolve circular dependency with following header file +//#include "Packet.h" +#include "Bytes.h" +#include "None.h" + +#include namespace RNS { @@ -14,6 +20,10 @@ namespace RNS { typedef void (*closed)(Link *link); }; + enum NoneConstructor { + NONE + }; + static constexpr const char* CURVE = Identity::CURVE; // The curve used for Elliptic Curve DH key exchanges @@ -51,20 +61,64 @@ namespace RNS { }; enum teardown_reasons { - TIMEOUT = 0x01, - INITIATOR_CLOSED = 0x02, - DESTINATION_CLOSED = 0x03, + TIMEOUT = 0x01, + INITIATOR_CLOSED = 0x02, + DESTINATION_CLOSED = 0x03, }; enum resource_strategies { - ACCEPT_NONE = 0x00, - ACCEPT_APP = 0x01, - ACCEPT_ALL = 0x02, + ACCEPT_NONE = 0x00, + ACCEPT_APP = 0x01, + ACCEPT_ALL = 0x02, }; public: + Link(NoneConstructor none) { + extreme("Link NONE object created"); + } + Link(RNS::NoneConstructor none) { + extreme("Link NONE object created"); + } + Link(const Link &link) : _object(link._object) { + extreme("Link object copy created"); + } Link(); - ~Link(); + virtual ~Link(){ + extreme("Link object destroyed"); + } + + inline Link& operator = (const Link &link) { + _object = link._object; + return *this; + } + inline operator bool() const { + return _object.get() != nullptr; + } + inline bool operator < (const Link &link) const { + return _object.get() < link._object.get(); + } + + public: + void set_link_id(const Packet &packet); + void receive(const Packet &packet); + + // getters/setters + inline const Bytes &link_id() const { assert(_object); return _object->_link_id; } + inline const Bytes &hash() const { assert(_object); return _object->_hash; } + + inline std::string toString() const { assert(_object); return "{Link: unknown}"; } + + private: + class Object { + public: + Object() {} + virtual ~Object() {} + private: + Bytes _link_id; + Bytes _hash; + friend class Link; + }; + std::shared_ptr _object; }; diff --git a/src/Log.cpp b/src/Log.cpp index 1850360..8c77d24 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -37,7 +37,7 @@ LogLevel RNS::loglevel() { return _level; } -void RNS::doLog(const char* msg, LogLevel level) { +void RNS::doLog(const char *msg, LogLevel level) { if (level > _level) { return; } @@ -50,3 +50,15 @@ void RNS::doLog(const char* msg, LogLevel level) { printf("%s: %s\n", getLevelName(level), msg); #endif } + +void RNS::head(const char *msg, LogLevel level) { + if (level > _level) { + return; + } +#ifndef NATIVE + Serial.println(""); +#else + printf("\n"); +#endif + doLog(msg, level); +} diff --git a/src/Log.h b/src/Log.h index 002379b..d4ad41d 100644 --- a/src/Log.h +++ b/src/Log.h @@ -22,36 +22,39 @@ namespace RNS { void loglevel(LogLevel level); LogLevel loglevel(); - void doLog(const char* msg, LogLevel level); + void doLog(const char *msg, LogLevel level); - inline void log(const char* msg, LogLevel level = LOG_NOTICE) { doLog(msg, 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 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 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 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 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 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 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 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 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); } + inline void extreme(const char *msg) { doLog(msg, LOG_EXTREME); } + inline void extreme(const std::string &msg) { doLog(msg.c_str(), LOG_EXTREME); } + + void head(const char *msg, LogLevel level = LOG_NOTICE); + inline void head(const std::string &msg, LogLevel level = LOG_NOTICE) { head(msg.c_str(), level); } } diff --git a/src/None.h b/src/None.h new file mode 100644 index 0000000..e435419 --- /dev/null +++ b/src/None.h @@ -0,0 +1,10 @@ +#pragma once + +namespace RNS { + + // generic empty object constructor type + enum NoneConstructor { + NONE + }; + +} diff --git a/src/Packet.cpp b/src/Packet.cpp index c2b45a4..3345028 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -11,7 +11,7 @@ using namespace RNS; Packet::Packet(const Destination &destination, const Interface &attached_interface, 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 Bytes &transport_id /*= Bytes::NONE*/, bool create_receipt /*= true*/) : _object(new Object(destination, attached_interface)) { if (_object->_destination) { - extreme("Creating packet with detination..."); + extreme("Creating packet with destination..."); // CBA TODO handle NONE if (transport_type == -1) { transport_type = Transport::BROADCAST; @@ -32,7 +32,7 @@ Packet::Packet(const Destination &destination, const Interface &attached_interfa _object->_create_receipt = create_receipt; } else { - extreme("Creating packet without detination..."); + extreme("Creating packet without destination..."); _object->_raw = data; _object->_packed = true; _object->_fromPacked = true; @@ -399,8 +399,8 @@ bool Packet::send() { } if (RNS::Transport::outbound(*this)) { - //zreturn self.receipt debug("Packet::send: successfully sent packet!!!"); + //zreturn self.receipt // MOCK return true; } @@ -428,8 +428,8 @@ bool Packet::resend() { pack(); if (RNS::Transport::outbound(*this)) { - //zreturn self.receipt debug("Packet::resend: successfully sent packet!!!"); + //zreturn self.receipt // MOCK return true; } @@ -441,24 +441,41 @@ bool Packet::resend() { } } +void Packet::prove(const Destination &destination /*= {Destination::NONE}*/) { +/* + assert(_object); + if (_object->_fromPacked && _object->_destination) { + if (_object->_destination.identity() && _object->_destination.identity().prv()) { + _object->_destination.identity().prove(*this, _object->_destination); + } + } + else if (_object->_fromPacked && _object->_link) { + _object->_link.prove_packet(*this); + } + else { + error("Could not prove packet associated with neither a destination nor a link"); + } +*/ +} + void Packet::update_hash() { assert(_object); _object->_packet_hash = get_hash(); } -Bytes Packet::get_hash() { +const Bytes Packet::get_hash() const { assert(_object); Bytes hashable_part = get_hashable_part(); return Identity::full_hash(hashable_part); } -Bytes Packet::getTruncatedHash() { +const Bytes Packet::getTruncatedHash() const { assert(_object); Bytes hashable_part = get_hashable_part(); return Identity::truncated_hash(hashable_part); } -Bytes Packet::get_hashable_part() { +const Bytes Packet::get_hashable_part() const { assert(_object); Bytes hashable_part; hashable_part << (uint8_t)(_object->_raw.data()[0] & 0b00001111); @@ -480,7 +497,7 @@ Bytes Packet::get_hashable_part() { //} -std::string Packet::debugString() { +std::string Packet::debugString() const { if (_object->_packed) { //unpack(); } @@ -495,15 +512,16 @@ std::string Packet::debugString() { dump += "transport: " + _object->_transport_id.toHex() + "\n"; dump += "destination: " + _object->_destination_hash.toHex() + "\n"; dump += "context_type: " + std::to_string(_object->_header_type) + "\n"; - dump += "data: " + _object->_data.toHex() + "\n"; - dump += " length: " + std::to_string(_object->_data.size()) + "\n"; dump += "raw: " + _object->_raw.toHex() + "\n"; dump += " length: " + std::to_string(_object->_raw.size()) + "\n"; + dump += "data: " + _object->_data.toHex() + "\n"; + dump += " length: " + std::to_string(_object->_data.size()) + "\n"; if (_object->_encrypted && _object->_raw.size() > 0) { size_t header_len = Reticulum::HEADER_MINSIZE; if (_object->_header_type == HEADER_2) { header_len = Reticulum::HEADER_MAXSIZE; } + dump += "encrypted:\n"; dump += " header: " + _object->_raw.left(header_len).toHex() + "\n"; dump += " key: " + _object->_raw.mid(header_len, Identity::KEYSIZE/8/2).toHex() + "\n"; Bytes ciphertext(_object->_raw.mid(header_len+Identity::KEYSIZE/8/2)); @@ -517,3 +535,23 @@ std::string Packet::debugString() { dump += "--------------------\n"; return dump; } + +void PacketReceipt::check_timeout() { + assert(_object); + if (_object->_status == SENT && is_timed_out()) { + if (_object->_timeout == -1) { + _object->_status = CULLED; + } + else { + _object->_status = FAILED; + } + + _object->_concluded_at = Utilities::OS::time(); + + if (_object->_callbacks._timeout) { + //zthread = threading.Thread(target=self.callbacks.timeout, args=(self,)) + //zthread.daemon = True + //zthread.start(); + } + } +} diff --git a/src/Packet.h b/src/Packet.h index 8dd8c7e..51ef17f 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -1,10 +1,13 @@ #pragma once -#include "Reticulum.h" -#include "Identity.h" #include "Transport.h" +#include "Reticulum.h" +#include "Link.h" +#include "Identity.h" #include "Destination.h" +#include "None.h" #include "Interfaces/Interface.h" +#include "Utilities/OS.h" #include #include @@ -12,11 +15,125 @@ namespace RNS { - class Packet; - class PacketProof; class ProofDestination; class PacketReceipt; - class PacketReceiptCallbacks; + class Packet; + + + class ProofDestination { + }; + + + /* + The PacketReceipt class is used to receive notifications about + :ref:`RNS.Packet` instances sent over the network. Instances + of this class are never created manually, but always returned from + the *send()* method of a :ref:`RNS.Packet` instance. + */ + class PacketReceipt { + + public: + class Callbacks { + public: + using delivery = void(*)(const PacketReceipt &packet_receipt); + using timeout = void(*)(const PacketReceipt &packet_receipt); + public: + delivery _delivery = nullptr; + timeout _timeout = nullptr; + friend class PacketReceipt; + }; + + enum NoneConstructor { + NONE + }; + + // Receipt status constants + enum Status { + FAILED = 0x00, + SENT = 0x01, + DELIVERED = 0x02, + CULLED = 0xFF + }; + + static const uint16_t EXPL_LENGTH = Identity::HASHLENGTH / 8 + Identity::SIGLENGTH / 8; + static const uint16_t IMPL_LENGTH = Identity::SIGLENGTH / 8; + + public: + PacketReceipt(NoneConstructor none) {} + PacketReceipt(const PacketReceipt &packet_receipt) : _object(packet_receipt._object) {} + PacketReceipt() : _object(new Object()) {} + PacketReceipt(const Packet &packet) {} + + inline PacketReceipt& operator = (const PacketReceipt &packet_receipt) { + _object = packet_receipt._object; + return *this; + } + inline operator bool() const { + return _object.get() != nullptr; + } + inline bool operator < (const PacketReceipt &packet_receipt) const { + return _object.get() < packet_receipt._object.get(); + } + + public: + inline bool is_timed_out() { + assert(_object); + return ((_object->_sent_at + _object->_timeout) < Utilities::OS::time()); + } + + void check_timeout(); + + /* + Sets a timeout in seconds + + :param timeout: The timeout in seconds. + */ + inline void set_timeout(int16_t timeout) { + assert(_object); + _object->_timeout = timeout; + } + + /* + Sets a function that gets called if a successfull delivery has been proven. + + :param callback: A *callable* with the signature *callback(packet_receipt)* + */ + inline void set_delivery_callback(Callbacks::delivery callback) { + assert(_object); + _object->_callbacks._delivery = callback; + } + + /* + Sets a function that gets called if the delivery times out. + + :param callback: A *callable* with the signature *callback(packet_receipt)* + */ + inline void set_timeout_callback(Callbacks::timeout callback) { + assert(_object); + _object->_callbacks._timeout = callback; + } + + private: + class Object { + public: + Object() {} + virtual ~Object() {} + private: + bool _sent = true; + uint64_t _sent_at = Utilities::OS::time(); + bool _proved = false; + Status _status = SENT; + Destination _destination = Destination::NONE; + Callbacks _callbacks; + uint64_t _concluded_at = 0; + //zPacket _proof_packet; + int16_t _timeout = 0; + friend class PacketReceipt; + }; + std::shared_ptr _object; + + }; + class Packet { @@ -85,25 +202,30 @@ namespace RNS { uint8_t EMPTY_DESTINATION[Reticulum::DESTINATION_LENGTH] = {0}; public: - Packet(const Destination &destination, const Interface &attached_interface, 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 Bytes &transport_id = Bytes::NONE, bool create_receipt = true); - 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 Bytes &transport_id = Bytes::NONE, bool create_receipt = true) : Packet(destination, Interface::NONE, data, packet_type, context, transport_type, header_type, transport_id, create_receipt) { - } Packet(NoneConstructor none) { extreme("Packet NONE object created"); } + Packet(RNS::NoneConstructor none) { + extreme("Packet NONE object created"); + } Packet(const Packet &packet) : _object(packet._object) { extreme("Packet object copy created"); } - ~Packet(); + Packet(const Destination &destination, const Interface &attached_interface, 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 Bytes &transport_id = Bytes::NONE, bool create_receipt = true); + 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 Bytes &transport_id = Bytes::NONE, bool create_receipt = true) : Packet(destination, Interface::NONE, data, packet_type, context, transport_type, header_type, transport_id, create_receipt) {} + virtual ~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())); + extreme("Packet object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); return *this; } inline operator bool() const { return _object.get() != nullptr; } + inline bool operator < (const Packet &packet) const { + return _object.get() < packet._object.get(); + } private: /* @@ -120,37 +242,59 @@ namespace RNS { bool unpack(); bool send(); bool resend(); + void prove(const Destination &destination = Destination::NONE); void update_hash(); - Bytes get_hash(); - Bytes getTruncatedHash(); - Bytes get_hashable_part(); + const Bytes get_hash() const; + const Bytes getTruncatedHash() const; + const Bytes get_hashable_part() const; //zProofDestination &generate_proof_destination(); // getters/setters + inline const Destination &destination() const { assert(_object); return _object->_destination; } + inline void destination(const Destination &destination) { assert(_object); _object->_destination = destination; } + inline const Link &link() const { assert(_object); return _object->_link; } + inline void link(const Link &link) { assert(_object); _object->_link = link; } + inline const Interface &attached_interface() const { assert(_object); return _object->_attached_interface; } inline const Interface &receiving_interface() const { assert(_object); return _object->_receiving_interface; } + inline void receiving_interface(const Interface &receiving_interface) { assert(_object); _object->_receiving_interface = receiving_interface; } inline header_types header_type() const { assert(_object); return _object->_header_type; } inline Transport::types transport_type() const { assert(_object); return _object->_transport_type; } inline Destination::types destination_type() const { assert(_object); return _object->_destination_type; } inline types packet_type() const { assert(_object); return _object->_packet_type; } inline context_types context() const { assert(_object); return _object->_context; } - inline const Bytes &data() const { assert(_object); return _object->_data; } - inline const Bytes &raw() const { assert(_object); return _object->_raw; } + inline bool sent() const { assert(_object); return _object->_sent; } + inline void sent(bool sent) { assert(_object); _object->_sent = sent; } + inline time_t sent_at() const { assert(_object); return _object->_sent_at; } + inline void sent_at(time_t sent_at) { assert(_object); _object->_sent_at = sent_at; } + inline bool create_receipt() const { assert(_object); return _object->_create_receipt; } + inline const PacketReceipt &receipt() const { assert(_object); return _object->_receipt; } + inline void receipt(const PacketReceipt &receipt) { assert(_object); _object->_receipt = receipt; } + inline uint8_t flags() const { assert(_object); return _object->_flags; } + inline uint8_t hops() const { assert(_object); return _object->_hops; } + inline void hops(uint8_t hops) { assert(_object); _object->_hops = hops; } inline Bytes packet_hash() const { assert(_object); return _object->_packet_hash; } + inline Bytes destination_hash() const { assert(_object); return _object->_destination_hash; } + inline Bytes transport_id() const { assert(_object); return _object->_transport_id; } + inline void transport_id(const Bytes &transport_id) { assert(_object); _object->_transport_id = transport_id; } + inline const Bytes &raw() const { assert(_object); return _object->_raw; } + inline const Bytes &data() const { assert(_object); return _object->_data; } inline std::string toString() const { assert(_object); return "{Packet:" + _object->_packet_hash.toHex() + "}"; } - std::string debugString(); + std::string debugString() const; private: class Object { public: Object(const Destination &destination, const Interface &attached_interface) : _destination(destination), _attached_interface(attached_interface) {} + virtual ~Object() {} private: - Destination _destination; + Destination _destination = {Destination::NONE}; + Link _link = {Link::NONE}; + + Interface _attached_interface = {Interface::NONE}; + Interface _receiving_interface = {Interface::NONE}; - Interface _attached_interface; - Interface _receiving_interface; - header_types _header_type = HEADER_1; Transport::types _transport_type = Transport::BROADCAST; Destination::types _destination_type = Destination::SINGLE; @@ -166,7 +310,7 @@ namespace RNS { bool _fromPacked = false; bool _truncated = false; // whether data was truncated bool _encrypted = false; // whether data is encrytpted - //z_receipt = nullptr; + PacketReceipt _receipt; uint16_t _mtu = Reticulum::MTU; time_t _sent_at = 0; @@ -186,20 +330,4 @@ namespace RNS { std::shared_ptr _object; }; - - 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 index a2399de..c47dccd 100644 --- a/src/Reticulum.cpp +++ b/src/Reticulum.cpp @@ -1,31 +1,134 @@ #include "Reticulum.h" +#include "Transport.h" #include "Log.h" #include using namespace RNS; +/* +Initialises and starts a Reticulum instance. This must be +done before any other operations, and Reticulum will not +pass any traffic before being instantiated. + +:param configdir: Full path to a Reticulum configuration directory. +*/ +//def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None): Reticulum::Reticulum() : _object(new Object()) { - extreme("Reticulum object created"); // Initialkize random number generator RNG.begin("Reticulum"); //RNG.stir(mac_address, sizeof(mac_address)); + +/* + RNS.vendor.platformutils.platform_checks() + + if configdir != None: + Reticulum.configdir = configdir + else: + if os.path.isdir("/etc/reticulum") and os.path.isfile("/etc/reticulum/config"): + Reticulum.configdir = "/etc/reticulum" + elif os.path.isdir(Reticulum.userdir+"/.config/reticulum") and os.path.isfile(Reticulum.userdir+"/.config/reticulum/config"): + Reticulum.configdir = Reticulum.userdir+"/.config/reticulum" + else: + Reticulum.configdir = Reticulum.userdir+"/.reticulum" + + if logdest == RNS.LOG_FILE: + RNS.logdest = RNS.LOG_FILE + RNS.logfile = Reticulum.configdir+"/logfile" + + Reticulum.configpath = Reticulum.configdir+"/config" + Reticulum.storagepath = Reticulum.configdir+"/storage" + Reticulum.cachepath = Reticulum.configdir+"/storage/cache" + Reticulum.resourcepath = Reticulum.configdir+"/storage/resources" + Reticulum.identitypath = Reticulum.configdir+"/storage/identities" + + Reticulum.__transport_enabled = False + Reticulum.__use_implicit_proof = True + Reticulum.__allow_probes = False + + Reticulum.panic_on_interface_error = False + + self.local_interface_port = 37428 + self.local_control_port = 37429 + self.share_instance = True + self.rpc_listener = None + + self.ifac_salt = Reticulum.IFAC_SALT + + self.requested_loglevel = loglevel + self.requested_verbosity = verbosity + if self.requested_loglevel != None: + if self.requested_loglevel > RNS.LOG_EXTREME: + self.requested_loglevel = RNS.LOG_EXTREME + if self.requested_loglevel < RNS.LOG_CRITICAL: + self.requested_loglevel = RNS.LOG_CRITICAL + + RNS.loglevel = self.requested_loglevel + + self.is_shared_instance = False + self.is_connected_to_shared_instance = False + self.is_standalone_instance = False + self.jobs_thread = None + self.last_data_persist = time.time() + self.last_cache_clean = 0 + + if not os.path.isdir(Reticulum.storagepath): + os.makedirs(Reticulum.storagepath) + + if not os.path.isdir(Reticulum.cachepath): + os.makedirs(Reticulum.cachepath) + + if not os.path.isdir(Reticulum.resourcepath): + os.makedirs(Reticulum.resourcepath) + + if not os.path.isdir(Reticulum.identitypath): + os.makedirs(Reticulum.identitypath) + + if os.path.isfile(self.configpath): + try: + self.config = ConfigObj(self.configpath) + except Exception as e: + RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR) + RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR) + RNS.panic() + else: + RNS.log("Could not load config file, creating default configuration file...") + self.__create_default_config() + RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.") + time.sleep(1.5) + + self.__apply_config() + RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE) + + RNS.Identity.load_known_destinations() +*/ + + Transport::start(*this); + +/* + self.rpc_addr = ("127.0.0.1", self.local_control_port) + self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key()) + + if self.is_shared_instance: + self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key) + thread = threading.Thread(target=self.rpc_loop) + thread.daemon = True + thread.start() + + atexit.register(Reticulum.exit_handler) + signal.signal(signal.SIGINT, Reticulum.sigint_handler) + signal.signal(signal.SIGTERM, Reticulum.sigterm_handler) +*/ + + extreme("Reticulum object created"); } Reticulum::~Reticulum() { extreme("Reticulum object destroyed"); } -/* -Returns whether proofs sent are explicit or implicit. - -:returns: True if the current running configuration specifies to use implicit proofs. False if not. -*/ -/*static*/ bool Reticulum::should_use_implicit_proof() { - return __use_implicit_proof; -} void Reticulum::loop() { // Perform random number gnerator housekeeping diff --git a/src/Reticulum.h b/src/Reticulum.h index 074eb44..7e1ec2b 100644 --- a/src/Reticulum.h +++ b/src/Reticulum.h @@ -1,10 +1,11 @@ #pragma once #include "Log.h" +#include "None.h" +#include #include #include -#include namespace RNS { @@ -86,31 +87,64 @@ namespace RNS { static const bool panic_on_interface_error = false; public: - Reticulum(); Reticulum(NoneConstructor none) { extreme("Reticulum NONE object created"); } + Reticulum(RNS::NoneConstructor none) { + extreme("Reticulum NONE object created"); + } Reticulum(const Reticulum &reticulum) : _object(reticulum._object) { extreme("Reticulum object copy created"); } - ~Reticulum(); + Reticulum(); + virtual ~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())); + extreme("Reticulum object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get())); return *this; } inline operator bool() const { return _object.get() != nullptr; } + inline bool operator < (const Reticulum &reticulum) const { + return _object.get() < reticulum._object.get(); + } public: - static bool should_use_implicit_proof(); void loop(); + /* + Returns whether proofs sent are explicit or implicit. + + :returns: True if the current running configuration specifies to use implicit proofs. False if not. + */ + inline static bool should_use_implicit_proof() { return __use_implicit_proof; } + + /* + Returns whether Transport is enabled for the running + instance. + + When Transport is enabled, Reticulum will + route traffic for other peers, respond to path requests + and pass announces over the network. + + :returns: True if Transport is enabled, False if not. + */ + inline static bool transport_enabled() { return __transport_enabled; } + + inline static bool probe_destination_enabled() { return __allow_probes; } + + // getters/setters + inline bool is_connected_to_shared_instance() const { assert(_object); return _object->_is_connected_to_shared_instance; } + private: class Object { + public: + Object() {} + virtual ~Object() {} private: + bool _is_connected_to_shared_instance = false; friend class Reticulum; }; std::shared_ptr _object; diff --git a/src/Test/TestBytes.cpp b/src/Test/TestBytes.cpp index 7e544cc..82250ed 100644 --- a/src/Test/TestBytes.cpp +++ b/src/Test/TestBytes.cpp @@ -1,4 +1,4 @@ -#include +//#include #include "Bytes.h" #include "Log.h" @@ -209,9 +209,9 @@ void testCowBytes() { assert(memcmp(bytes3.data(), "1", bytes3.size()) == 0); assert(bytes3.data() == bytes2.data()); - RNS::extreme("pre bytes1 ptr: " + std::to_string((uint32_t)bytes1.data()) + " data: " + bytes1.toString()); - RNS::extreme("pre bytes2 ptr: " + std::to_string((uint32_t)bytes2.data()) + " data: " + bytes2.toString()); - RNS::extreme("pre bytes3 ptr: " + std::to_string((uint32_t)bytes3.data()) + " data: " + bytes3.toString()); + RNS::extreme("pre bytes1 ptr: " + std::to_string((uintptr_t)bytes1.data()) + " data: " + bytes1.toString()); + RNS::extreme("pre bytes2 ptr: " + std::to_string((uintptr_t)bytes2.data()) + " data: " + bytes2.toString()); + RNS::extreme("pre bytes3 ptr: " + std::to_string((uintptr_t)bytes3.data()) + " data: " + bytes3.toString()); //bytes1.append("mississippi"); //assert(bytes1.size() == 12); @@ -228,9 +228,9 @@ void testCowBytes() { assert(memcmp(bytes3.data(), "mississippi", bytes3.size()) == 0); assert(bytes3.data() != bytes2.data()); - RNS::extreme("post bytes1 ptr: " + std::to_string((uint32_t)bytes1.data()) + " data: " + bytes1.toString()); - RNS::extreme("post bytes2 ptr: " + std::to_string((uint32_t)bytes2.data()) + " data: " + bytes2.toString()); - RNS::extreme("post bytes3 ptr: " + std::to_string((uint32_t)bytes3.data()) + " data: " + bytes3.toString()); + RNS::extreme("post bytes1 ptr: " + std::to_string((uintptr_t)bytes1.data()) + " data: " + bytes1.toString()); + RNS::extreme("post bytes2 ptr: " + std::to_string((uintptr_t)bytes2.data()) + " data: " + bytes2.toString()); + RNS::extreme("post bytes3 ptr: " + std::to_string((uintptr_t)bytes3.data()) + " data: " + bytes3.toString()); } void testBytesConversion() { @@ -331,8 +331,15 @@ void testMap() } /* -int main(void) -{ +void setUp(void) { + // set stuff up here +} + +void tearDown(void) { + // clean stuff up here +} + +int runUnityTests(void) { UNITY_BEGIN(); RUN_TEST(testBytes); RUN_TEST(testCowBytes); @@ -340,4 +347,24 @@ int main(void) RUN_TEST(testMap); return UNITY_END(); } + +// For native dev-platform or for some embedded frameworks +int main(void) { + return runUnityTests(); +} + +// For Arduino framework +void setup() { + // Wait ~2 seconds before the Unity test runner + // establishes connection with a board Serial interface + delay(2000); + + runUnityTests(); +} +void loop() {} + +// For ESP-IDF framework +void app_main() { + runUnityTests(); +} */ diff --git a/src/Test/TestCrypto.cpp b/src/Test/TestCrypto.cpp index cbfc02e..7d44371 100644 --- a/src/Test/TestCrypto.cpp +++ b/src/Test/TestCrypto.cpp @@ -1,4 +1,4 @@ -#include +//#include #include "Reticulum.h" #include "Identity.h" diff --git a/src/Test/TestReference.cpp b/src/Test/TestReference.cpp index 2a41c55..d183902 100644 --- a/src/Test/TestReference.cpp +++ b/src/Test/TestReference.cpp @@ -1,4 +1,4 @@ -#include +//#include #include "Reticulum.h" #include "Bytes.h" diff --git a/src/Transport.cpp b/src/Transport.cpp index 23681d0..b2dd6b4 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -1,43 +1,2889 @@ #include "Transport.h" +#include "Reticulum.h" #include "Destination.h" - +#include "Identity.h" +#include "Packet.h" #include "Log.h" +#include "Interfaces/Interface.h" +#include "Cryptography/Random.h" +#include "Utilities/OS.h" + +#include +#include +#include using namespace RNS; +using namespace RNS::Utilities; -Transport::Transport() { - log("Transport object created", LOG_EXTREME); +///*static*/ std::set, std::less> Transport::_interfaces; +/*static*/ std::list> Transport::_interfaces; +/*static*/ std::set Transport::_destinations; +/*static*/ std::set Transport::_pending_links; +/*static*/ std::set Transport::_active_links; +/*static*/ std::set Transport::_packet_hashlist; +/*static*/ std::set Transport::_receipts; + +/*static*/ std::map Transport::_announce_table; +/*static*/ std::map Transport::_destination_table; +/*static*/ std::map Transport::_reverse_table; +/*static*/ std::map Transport::_link_table; +/*static*/ std::set Transport::_announce_handlers; +/*static*/ std::set Transport::_path_requests; + +/*static*/ uint16_t Transport::_max_pr_taXgxs = 32000; + +/*static*/ std::set Transport::_control_destinations; +/*static*/ std::set Transport::_control_hashes; + +/*static*/ std::set Transport::_local_client_interfaces; + +/*static*/ uint16_t Transport::_LOCAL_CLIENT_CACHE_MAXSIZE = 512; + +/*static*/ uint64_t Transport::_start_time = 0; +/*static*/ bool Transport::_jobs_locked = false; +/*static*/ bool Transport::_jobs_running = false; +/*static*/ uint32_t Transport::_job_interval = 250; +/*static*/ uint64_t Transport::_links_last_checked = 0; +/*static*/ uint32_t Transport::_links_check_interval = 1000; +/*static*/ uint64_t Transport::_receipts_last_checked = 0; +/*static*/ uint32_t Transport::_receipts_check_interval = 1000; +/*static*/ uint64_t Transport::_announces_last_checked = 0; +/*static*/ uint32_t Transport::_announces_check_interval= 1000; +/*static*/ uint32_t Transport::_hashlist_maxsize = 1000000; +/*static*/ uint64_t Transport::_tables_last_culled = 0; +/*static*/ uint32_t Transport::_tables_cull_interval = 5000; + +/*static*/ Reticulum Transport::_owner(Reticulum::NONE); +/*static*/ Identity Transport::_identity(Identity::NONE); + +/*static*/ void Transport::start(const Reticulum &reticulum_instance) { + _jobs_running = true; + _owner = reticulum_instance; + + if (!_identity) { + //ztransport_identity_path = Reticulum::storagepath+"/transport_identity" + //zif (os.path.isfile(transport_identity_path)) { + //z identity = Identity.from_file(transport_identity_path); + //z} + + if (!_identity) { + verbose("No valid Transport Identity in storage, creating..."); + _identity = new Identity(); + //z_identity.to_file(transport_identity_path); + } + else { + verbose("Loaded Transport Identity from storage"); + } + } + +/* + packet_hashlist_path = Reticulum::storagepath + "/packet_hashlist"; + if (!owner.is_connected_to_shared_instance()) { + if (os.path.isfile(packet_hashlist_path)) { + try { + file = open(packet_hashlist_path, "rb"); + packet_hashlist = umsgpack.unpackb(file.read()); + file.close(); + } + catch (std::exception &e) { + error("Could not load packet hashlist from storage, the contained exception was: " + e.what()); + } + } + } + + // Create transport-specific destinations + Transport.path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") + Transport.path_request_destination.set_packet_callback(Transport.path_request_handler) + Transport.control_destinations.append(Transport.path_request_destination) + Transport.control_hashes.append(Transport.path_request_destination.hash) + + Transport.tunnel_synthesize_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize") + Transport.tunnel_synthesize_destination.set_packet_callback(Transport.tunnel_synthesize_handler) + Transport.control_destinations.append(Transport.tunnel_synthesize_handler) + Transport.control_hashes.append(Transport.tunnel_synthesize_destination.hash) +*/ + + _jobs_running = false; + +/* + thread = threading.Thread(target=Transport.jobloop, daemon=True) + thread.start() + + if RNS.Reticulum.transport_enabled(): + destination_table_path = RNS.Reticulum.storagepath+"/destination_table" + tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels" + + if os.path.isfile(destination_table_path) and not Transport.owner.is_connected_to_shared_instance: + serialised_destinations = [] + try: + file = open(destination_table_path, "rb") + serialised_destinations = umsgpack.unpackb(file.read()) + file.close() + + for serialised_entry in serialised_destinations: + destination_hash = serialised_entry[0] + + if len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8: + timestamp = serialised_entry[1] + received_from = serialised_entry[2] + hops = serialised_entry[3] + expires = serialised_entry[4] + random_blobs = serialised_entry[5] + receiving_interface = Transport.find_interface_from_hash(serialised_entry[6]) + announce_packet = Transport.get_cached_packet(serialised_entry[7]) + + if announce_packet != None and receiving_interface != None: + announce_packet.unpack() + // We increase the hops, since reading a packet + // from cache is equivalent to receiving it again + // over an interface. It is cached with it's non- + // increased hop-count. + announce_packet.hops += 1 + Transport.destination_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet] + RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG) + else: + RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) + if announce_packet == None: + RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG) + if receiving_interface == None: + RNS.log("The interface is no longer available", RNS.LOG_DEBUG) + + if len(Transport.destination_table) == 1: + specifier = "entry" + else: + specifier = "entries" + + RNS.log("Loaded "+str(len(Transport.destination_table))+" path table "+specifier+" from storage", RNS.LOG_VERBOSE) + + except Exception as e: + RNS.log("Could not load destination table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance: + serialised_tunnels = [] + try: + file = open(tunnel_table_path, "rb") + serialised_tunnels = umsgpack.unpackb(file.read()) + file.close() + + for serialised_tunnel in serialised_tunnels: + tunnel_id = serialised_tunnel[0] + interface_hash = serialised_tunnel[1] + serialised_paths = serialised_tunnel[2] + expires = serialised_tunnel[3] + + tunnel_paths = {} + for serialised_entry in serialised_paths: + destination_hash = serialised_entry[0] + timestamp = serialised_entry[1] + received_from = serialised_entry[2] + hops = serialised_entry[3] + expires = serialised_entry[4] + random_blobs = serialised_entry[5] + receiving_interface = Transport.find_interface_from_hash(serialised_entry[6]) + announce_packet = Transport.get_cached_packet(serialised_entry[7]) + + if announce_packet != None: + announce_packet.unpack() + // We increase the hops, since reading a packet + // from cache is equivalent to receiving it again + // over an interface. It is cached with it's non- + // increased hop-count. + announce_packet.hops += 1 + + tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet] + tunnel_paths[destination_hash] = tunnel_path + + tunnel = [tunnel_id, None, tunnel_paths, expires] + Transport.tunnels[tunnel_id] = tunnel + + if len(Transport.destination_table) == 1: + specifier = "entry" + else: + specifier = "entries" + + RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE) + + except Exception as e: + RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + if RNS.Reticulum.probe_destination_enabled(): + Transport.probe_destination = RNS.Destination(Transport.identity, RNS.Destination.IN, RNS.Destination.SINGLE, Transport.APP_NAME, "probe") + Transport.probe_destination.accepts_links(False) + Transport.probe_destination.set_proof_strategy(RNS.Destination.PROVE_ALL) + Transport.probe_destination.announce() + RNS.log("Transport Instance will respond to probe requests on "+str(Transport.probe_destination), RNS.LOG_NOTICE) + else: + Transport.probe_destination = None + + RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE) + Transport.start_time = time.time() + + // Synthesize tunnels for any interfaces wanting it + for interface in Transport.interfaces: + interface.tunnel_id = None + if hasattr(interface, "wants_tunnel") and interface.wants_tunnel: + Transport.synthesize_tunnel(interface) +*/ } -Transport::~Transport() { - log("Transport object destroyed", LOG_EXTREME); +/*static*/ void Transport::jobloop() { + while (true) { + jobs(); + OS::sleep(_job_interval); + } } +/*static*/ void Transport::jobs() { + + std::vector outgoing; + std::vector path_requests; + _jobs_running = true; + + try { +/* + if (!Transport.jobs_locked) { + + // Process active and pending link lists + if (time(nullptr) > _links_last_checked + Transport.links_check_interval) { + + for (auto &link : _pending_links) { + if link.status() == RNS.Link.CLOSED: + // If we are not a Transport Instance, finding a pending link + // that was never activated will trigger an expiry of the path + // to the destination, and an attempt to rediscover the path. + if not RNS.Reticulum.transport_enabled(): + Transport.expire_path(link.destination.hash) + + // If we are connected to a shared instance, it will take + // care of sending out a new path request. If not, we will + // send one directly. + if not Transport.owner.is_connected_to_shared_instance: + last_path_request = 0 + if link.destination.hash in Transport.path_requests: + last_path_request = Transport.path_requests[link.destination.hash] + + if time.time() - last_path_request > Transport.PATH_REQUEST_MI: + RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link.destination.hash)+" since an attempted link was never established", RNS.LOG_DEBUG) + if not link.destination.hash in path_requests: + path_requests.append(link.destination.hash) + + Transport.pending_links.remove(link) + } + for (auto &link : _active_links) { + if link.status == RNS.Link.CLOSED: + Transport.active_links.remove(link) + } + + _links_last_checked = time(NULL) + + // Process receipts list for timed-out packets + if time.time() > Transport.receipts_last_checked+Transport.receipts_check_interval: + while len(Transport.receipts) > Transport.MAX_RECEIPTS: + culled_receipt = Transport.receipts.pop(0) + culled_receipt.timeout = -1 + culled_receipt.check_timeout() + + for receipt in Transport.receipts: + receipt.check_timeout() + if receipt.status != RNS.PacketReceipt.SENT: + if receipt in Transport.receipts: + Transport.receipts.remove(receipt) + + Transport.receipts_last_checked = time.time() + + // Process announces needing retransmission + if time.time() > Transport.announces_last_checked+Transport.announces_check_interval: + for destination_hash in Transport.announce_table: + announce_entry = Transport.announce_table[destination_hash] + if announce_entry[2] > Transport.PATHFINDER_R: + RNS.log("Completed announce processing for "+RNS.prettyhexrep(destination_hash)+", retry limit reached", RNS.LOG_EXTREME) + Transport.announce_table.pop(destination_hash) + break + else: + if time.time() > announce_entry[1]: + announce_entry[1] = time.time() + Transport.PATHFINDER_G + Transport.PATHFINDER_RW + announce_entry[2] += 1 + packet = announce_entry[5] + block_rebroadcasts = announce_entry[7] + attached_interface = announce_entry[8] + announce_context = RNS.Packet.NONE + if block_rebroadcasts: + announce_context = RNS.Packet.PATH_RESPONSE + announce_data = packet.data + announce_identity = RNS.Identity.recall(packet.destination_hash) + announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown"); + announce_destination.hash = packet.destination_hash + announce_destination.hexhash = announce_destination.hash.hex() + + new_packet = RNS.Packet( + announce_destination, + announce_data, + RNS.Packet.ANNOUNCE, + context = announce_context, + header_type = RNS.Packet.HEADER_2, + transport_type = Transport.TRANSPORT, + transport_id = Transport.identity.hash, + attached_interface = attached_interface + ) + + new_packet.hops = announce_entry[4] + if block_rebroadcasts: + RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) + else: + RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) + + outgoing.append(new_packet) + + // This handles an edge case where a peer sends a past + // request for a destination just after an announce for + // said destination has arrived, but before it has been + // rebroadcast locally. In such a case the actual announce + // is temporarily held, and then reinserted when the path + // request has been served to the peer. + if destination_hash in Transport.held_announces: + held_entry = Transport.held_announces.pop(destination_hash) + Transport.announce_table[destination_hash] = held_entry + RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG) + + Transport.announces_last_checked = time.time() + + + // Cull the packet hashlist if it has reached its max size + if len(Transport.packet_hashlist) > Transport.hashlist_maxsize: + Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1] + + // Cull the path request tags list if it has reached its max size + if len(Transport.discovery_pr_tags) > Transport.max_pr_tags: + Transport.discovery_pr_tags = Transport.discovery_pr_tags[len(Transport.discovery_pr_tags)-Transport.max_pr_tags:len(Transport.discovery_pr_tags)-1] + + if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval: + // Cull the reverse table according to timeout + stale_reverse_entries = [] + for truncated_packet_hash in Transport.reverse_table: + reverse_entry = Transport.reverse_table[truncated_packet_hash] + if time.time() > reverse_entry[2] + Transport.REVERSE_TIMEOUT: + stale_reverse_entries.append(truncated_packet_hash) + + // Cull the link table according to timeout + stale_links = [] + for link_id in Transport.link_table: + link_entry = Transport.link_table[link_id] + + if link_entry[7] == True: + if time.time() > link_entry[0] + Transport.LINK_TIMEOUT: + stale_links.append(link_id) + else: + if time.time() > link_entry[8]: + stale_links.append(link_id) + + last_path_request = 0 + if link_entry[6] in Transport.path_requests: + last_path_request = Transport.path_requests[link_entry[6]] + + lr_taken_hops = link_entry[5] + + path_request_throttle = time.time() - last_path_request < Transport.PATH_REQUEST_MI + path_request_conditions = False + + // If the path has been invalidated between the time of + // making the link request and now, try to rediscover it + if not Transport.has_path(link_entry[6]): + RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted link was never established, and path is now missing", RNS.LOG_DEBUG) + path_request_conditions =True + + // If this link request was originated from a local client + // attempt to rediscover a path to the destination, if this + // has not already happened recently. + elif not path_request_throttle and lr_taken_hops == 0: + RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted local client link was never established", RNS.LOG_DEBUG) + path_request_conditions = True + + // If the link destination was previously only 1 hop + // away, this likely means that it was local to one + // of our interfaces, and that it roamed somewhere else. + // In that case, try to discover a new path. + elif not path_request_throttle and Transport.hops_to(link_entry[6]) == 1: + RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted link was never established, and destination was previously local to an interface on this instance", RNS.LOG_DEBUG) + path_request_conditions = True + + // If the link destination was previously only 1 hop + // away, this likely means that it was local to one + // of our interfaces, and that it roamed somewhere else. + // In that case, try to discover a new path. + elif not path_request_throttle and lr_taken_hops == 1: + RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[6])+" since an attempted link was never established, and link initiator is local to an interface on this instance", RNS.LOG_DEBUG) + path_request_conditions = True + + if path_request_conditions: + if not link_entry[6] in path_requests: + path_requests.append(link_entry[6]) + + if not RNS.Reticulum.transport_enabled(): + // Drop current path if we are not a transport instance, to + // allow using higher-hop count paths or reused announces + // from newly adjacent transport instances. + Transport.expire_path(link_entry[6]) + + // Cull the path table + stale_paths = [] + for destination_hash in Transport.destination_table: + destination_entry = Transport.destination_table[destination_hash] + attached_interface = destination_entry[5] + + if attached_interface != None and hasattr(attached_interface, "mode") and attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT: + destination_expiry = destination_entry[0] + Transport.AP_PATH_TIME + elif attached_interface != None and hasattr(attached_interface, "mode") and attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING: + destination_expiry = destination_entry[0] + Transport.ROAMING_PATH_TIME + else: + destination_expiry = destination_entry[0] + Transport.DESTINATION_TIMEOUT + + if time.time() > destination_expiry: + stale_paths.append(destination_hash) + RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG) + elif not attached_interface in Transport.interfaces: + stale_paths.append(destination_hash) + RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG) + + // Cull the pending discovery path requests table + stale_discovery_path_requests = [] + for destination_hash in Transport.discovery_path_requests: + entry = Transport.discovery_path_requests[destination_hash] + + if time.time() > entry["timeout"]: + stale_discovery_path_requests.append(destination_hash) + RNS.log("Waiting path request for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG) + + // Cull the tunnel table + stale_tunnels = [] + ti = 0 + for tunnel_id in Transport.tunnels: + tunnel_entry = Transport.tunnels[tunnel_id] + + expires = tunnel_entry[3] + if time.time() > expires: + stale_tunnels.append(tunnel_id) + RNS.log("Tunnel "+RNS.prettyhexrep(tunnel_id)+" timed out and was removed", RNS.LOG_EXTREME) + else: + stale_tunnel_paths = [] + tunnel_paths = tunnel_entry[2] + for tunnel_path in tunnel_paths: + tunnel_path_entry = tunnel_paths[tunnel_path] + + if time.time() > tunnel_path_entry[0] + Transport.DESTINATION_TIMEOUT: + stale_tunnel_paths.append(tunnel_path) + RNS.log("Tunnel path to "+RNS.prettyhexrep(tunnel_path)+" timed out and was removed", RNS.LOG_EXTREME) + + for tunnel_path in stale_tunnel_paths: + tunnel_paths.pop(tunnel_path) + ti += 1 + + + if ti > 0: + if ti == 1: + RNS.log("Removed "+str(ti)+" tunnel path", RNS.LOG_EXTREME) + else: + RNS.log("Removed "+str(ti)+" tunnel paths", RNS.LOG_EXTREME) + + + + i = 0 + for truncated_packet_hash in stale_reverse_entries: + Transport.reverse_table.pop(truncated_packet_hash) + i += 1 + + if i > 0: + if i == 1: + RNS.log("Released "+str(i)+" reverse table entry", RNS.LOG_EXTREME) + else: + RNS.log("Released "+str(i)+" reverse table entries", RNS.LOG_EXTREME) + + + + i = 0 + for link_id in stale_links: + Transport.link_table.pop(link_id) + i += 1 + + if i > 0: + if i == 1: + RNS.log("Released "+str(i)+" link", RNS.LOG_EXTREME) + else: + RNS.log("Released "+str(i)+" links", RNS.LOG_EXTREME) + + i = 0 + for destination_hash in stale_paths: + Transport.destination_table.pop(destination_hash) + i += 1 + + if i > 0: + if i == 1: + RNS.log("Removed "+str(i)+" path", RNS.LOG_EXTREME) + else: + RNS.log("Removed "+str(i)+" paths", RNS.LOG_EXTREME) + + i = 0 + for destination_hash in stale_discovery_path_requests: + Transport.discovery_path_requests.pop(destination_hash) + i += 1 + + if i > 0: + if i == 1: + RNS.log("Removed "+str(i)+" waiting path request", RNS.LOG_EXTREME) + else: + RNS.log("Removed "+str(i)+" waiting path requests", RNS.LOG_EXTREME) + + i = 0 + for tunnel_id in stale_tunnels: + Transport.tunnels.pop(tunnel_id) + i += 1 + + if i > 0: + if i == 1: + RNS.log("Removed "+str(i)+" tunnel", RNS.LOG_EXTREME) + else: + RNS.log("Removed "+str(i)+" tunnels", RNS.LOG_EXTREME) + + Transport.tables_last_culled = time.time() + + else: + // Transport jobs were locked, do nothing + pass +*/ + } + catch (std::exception &e) { + error("An exception occurred while running Transport jobs."); + error("The contained exception was: " + std::string(e.what())); + } + + _jobs_running = false; + + for (auto &packet : outgoing) { + packet.send(); + } + + for (auto &destination_hash : path_requests) { + request_path(destination_hash); + } +} + +/*static*/ void Transport::transmit(Interface &interface, const Bytes &raw) { + try { + //if hasattr(interface, "ifac_identity") and interface.ifac_identity != None: + if (interface.ifac_identity()) { +/* + // Calculate packet access code + ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:] + + // Generate mask + mask = RNS.Cryptography.hkdf( + length=len(raw)+interface.ifac_size, + derive_from=ifac, + salt=interface.ifac_key, + context=None, + ) + + // Set IFAC flag + new_header = bytes([raw[0] | 0x80, raw[1]]) + + // Assemble new payload with IFAC + new_raw = new_header+ifac+raw[2:] + + // Mask payload + i = 0; masked_raw = b"" + for byte in new_raw: + if i == 0: + // Mask first header byte, but make sure the + // IFAC flag is still set + masked_raw += bytes([byte ^ mask[i] | 0x80]) + elif i == 1 or i > interface.ifac_size+1: + // Mask second header byte and payload + masked_raw += bytes([byte ^ mask[i]]) + else: + // Don't mask the IFAC itself + masked_raw += bytes([byte]) + i += 1 + + // Send it + interface.processOutgoing(masked_raw) +*/ + } + else { + interface.processOutgoing(raw); + } + } + catch (std::exception &e) { + error("Error while transmitting on " + interface.toString() + ". The contained exception was: " + e.what()); + } +} + +/*static*/ bool Transport::outbound(Packet &packet) { + extreme("Transport::outbound()"); + + extreme("Transport::outbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops())); + + while (_jobs_running) { + extreme("Transport::outbound: sleeping..."); + OS::sleep(5); + } + + _jobs_locked = true; + + bool sent = false; + uint64_t outbound_time = OS::time(); + + // Check if we have a known path for the destination in the path table + //if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination.type != RNS.Destination.PLAIN and packet.destination.type != RNS.Destination.GROUP and packet.destination_hash in Transport.destination_table: + if (packet.packet_type() != Packet::ANNOUNCE && packet.destination().type() != Destination::PLAIN && packet.destination().type() != Destination::GROUP && _destination_table.find(packet.destination_hash()) != _destination_table.end()) { + extreme("Transport::outbound: Path to destination is known"); + //outbound_interface = Transport.destination_table[packet.destination_hash][5] + DestinationEntry destination_entry = (*_destination_table.find(packet.destination_hash())).second; + Interface &outbound_interface = destination_entry._receiving_interface; + + // If there's more than one hop to the destination, and we know + // a path, we insert the packet into transport by adding the next + // transport nodes address to the header, and modifying the flags. + // This rule applies both for "normal" transport, and when connected + // to a local shared Reticulum instance. + //if Transport.destination_table[packet.destination_hash][2] > 1: + if (destination_entry._hops > 1) { + extreme("Forwarding packet to next closest interface..."); + if (packet.header_type() == Packet::HEADER_1) { + // Insert packet into transport + //new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111) + uint8_t new_flags = (Packet::HEADER_2) << 6 | (Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111); + Bytes new_raw; + //new_raw = struct.pack("!B", new_flags) + new_raw << new_flags; + //new_raw += packet.raw[1:2] + new_raw << packet.raw().mid(1,1); + //new_raw += Transport.destination_table[packet.destination_hash][1] + new_raw << destination_entry._received_from; + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(outbound_interface, new_raw); + //_destination_table[packet.destination_hash][0] = time.time() + destination_entry._timestamp = OS::time(); + sent = true; + } + } + + // In the special case where we are connected to a local shared + // Reticulum instance, and the destination is one hop away, we + // also add transport headers to inject the packet into transport + // via the shared instance. Normally a packet for a destination + // one hop away would just be broadcast directly, but since we + // are "behind" a shared instance, we need to get that instance + // to transport it onto the network. + //elif Transport.destination_table[packet.destination_hash][2] == 1 and Transport.owner.is_connected_to_shared_instance: + else if (destination_entry._hops == 1 && _owner.is_connected_to_shared_instance()) { + extreme("Transport::outbound: Sending packet for directly connected interface to shared instance..."); + if (packet.header_type() == Packet::HEADER_1) { + // Insert packet into transport + //new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111) + uint8_t new_flags = (Packet::HEADER_2) << 6 | (Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111); + Bytes new_raw; + //new_raw = struct.pack("!B", new_flags) + new_raw << new_flags; + //new_raw += packet.raw[1:2] + new_raw << packet.raw().mid(1, 1); + //new_raw += Transport.destination_table[packet.destination_hash][1] + new_raw << destination_entry._received_from; + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(outbound_interface, new_raw); + //Transport.destination_table[packet.destination_hash][0] = time.time() + destination_entry._timestamp = OS::time(); + sent = true; + } + } + + // If none of the above applies, we know the destination is + // directly reachable, and also on which interface, so we + // simply transmit the packet directly on that one. + else { + extreme("Transport::outbound: Sending packet over directly connected interface..."); + transmit(outbound_interface, packet.raw()); + sent = true; + } + } + // If we don't have a known path for the destination, we'll + // broadcast the packet on all outgoing interfaces, or the + // just the relevant interface if the packet has an attached + // interface, or belongs to a link. + else { + extreme("Transport::outbound: Path to destination is unknown"); + bool stored_hash = false; + for (const Interface &interface : _interfaces) { + extreme("Transport::outbound: Checking interface " + interface.toString()); + if (interface.OUT()) { + bool should_transmit = true; + + if (packet.destination().type() == Destination::LINK) { + if (packet.destination().status() == Link::CLOSED) { + should_transmit = false; + } + // CBA Destination has no member attached_interface + //zif (interface != packet.destination().attached_interface()) { + //z should_transmit = false; + //z} + } + + if (packet.attached_interface() && interface != packet.attached_interface()) { + should_transmit = false; + } + + if (packet.packet_type() == Packet::ANNOUNCE) { + if (!packet.attached_interface()) { + extreme("Transport::outbound: Packet has no attached interface"); + if (interface.mode() == Interface::MODE_ACCESS_POINT) { + extreme("Blocking announce broadcast on " + interface.toString() + " due to AP mode"); + should_transmit = false; + } + else if (interface.mode() == Interface::MODE_ROAMING) { + //local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) + //Destination local_destination(Destination::NONE); + bool found_local = false; + for (auto &destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + //local_destination = destination; + found_local = true; + break; + } + } + //if local_destination != None: + //if (local_destination) { + if (found_local) { + //extreme("Allowing announce broadcast on roaming-mode interface from instance-local destination"); + } + else { + const Interface &from_interface = next_hop_interface(packet.destination_hash()); + //if from_interface == None or not hasattr(from_interface, "mode"): + if (!from_interface || from_interface.mode() == Interface::MODE_NONE) { + should_transmit = false; + if (!from_interface) { + extreme("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist"); + } + else if (from_interface.mode() == Interface::MODE_NONE) { + extreme("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured"); + } + } + else { + if (from_interface.mode() == Interface::MODE_ROAMING) { + extreme("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface"); + should_transmit = false; + } + else if (from_interface.mode() == Interface::MODE_BOUNDARY) { + extreme("Blocking announce broadcast on " + interface.toString() + " due to boundary-mode next-hop interface"); + should_transmit = false; + } + } + } + } + else if (interface.mode() == Interface::MODE_BOUNDARY) { + //local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) + // next and filter pattern? + // next(iterable, default) + // list comprehension: [x for x in xyz if x in a] + // CBA TODO confirm that above pattern just selects the first matching destination + //Destination local_destination(Destination::NONE); + bool found_local = false; + for (auto &destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + //local_destination = destination; + found_local = true; + break; + } + } + //if local_destination != None: + //if (local_destination) { + if (found_local) { + //extreme("Allowing announce broadcast on boundary-mode interface from instance-local destination"); + } + else { + const Interface &from_interface = next_hop_interface(packet.destination_hash()); + if (!from_interface || from_interface.mode() == Interface::MODE_NONE) { + should_transmit = false; + if (!from_interface) { + extreme("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist"); + } + else if (from_interface.mode() == Interface::MODE_NONE) { + extreme("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured"); + } + } + else { + if (from_interface.mode() == Interface::MODE_ROAMING) { + extreme("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface"); + should_transmit = false; + } + } + } + } + else { + // Currently, annouces originating locally are always + // allowed, and do not conform to bandwidth caps. + // TODO: Rethink whether this is actually optimal. + if (packet.hops() > 0) { + +/* + if not hasattr(interface, "announce_cap"): + interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP + + if not hasattr(interface, "announce_allowed_at"): + interface.announce_allowed_at = 0 + + if not hasattr(interface, "announce_queue"): + interface.announce_queue = [] +*/ + + bool queued_announces = (interface.announce_queue().size() > 0); + if (!queued_announces && outbound_time > interface.announce_allowed_at()) { + uint16_t tx_time = (packet.raw().size() * 8) / interface.bitrate(); + uint16_t wait_time = (tx_time / interface.announce_cap()); + const_cast(interface).announce_allowed_at(outbound_time + wait_time); + } + else { + should_transmit = false; + if (interface.announce_queue().size() < Reticulum::MAX_QUEUED_ANNOUNCES) { + bool should_queue = true; + for (auto &entry : interface.announce_queue()) { + if (entry._destination == packet.destination_hash()) { + uint64_t emission_timestamp = announce_emitted(packet); + should_queue = false; + if (emission_timestamp > entry._emitted) { + entry._time = outbound_time; + entry._hops = packet.hops(); + entry._emitted = emission_timestamp; + entry._raw = packet.raw(); + } + break; + } + } + if (should_queue) { + Interface::AnnounceEntry entry( + packet.destination_hash(), + outbound_time, + packet.hops(), + announce_emitted(packet), + packet.raw() + ); + + queued_announces = (interface.announce_queue().size() > 0); + const_cast(interface).add_announce(entry); + + if (!queued_announces) { + uint64_t wait_time = std::max(interface.announce_allowed_at() - OS::time(), (uint64_t)0); + // CBA TODO THREAD? + //ztimer = threading.Timer(wait_time, interface.process_announce_queue) + //ztimer.start() + + std::string wait_time_str; + if (wait_time < 1000) { + wait_time_str = std::to_string(wait_time) + "ms"; + } + else { + wait_time_str = std::to_string(OS::round(wait_time/1000,1)) + "s"; + } + + std::string ql_str = std::to_string(interface.announce_queue().size()); + extreme("Added announce to queue (height " + ql_str + ") on " + interface.toString() + " for processing in " + wait_time_str); + } + else { + uint64_t wait_time = std::max(interface.announce_allowed_at() - OS::time(), (uint64_t)0); + + std::string wait_time_str; + if (wait_time < 1000) { + wait_time_str = std::to_string(wait_time) + "ms"; + } + else { + wait_time_str = std::to_string(OS::round(wait_time/1000,2)) + "s"; + } + + std::string ql_str = std::to_string(interface.announce_queue().size()); + extreme("Added announce to queue (height " + ql_str + ") on " + interface.toString() + " for processing in " + wait_time_str); + } + } + } + else { + // future + } + } + } + else { + // future + } + } + } + } + + if (should_transmit) { + extreme("Transport::outbound: Packet transmission allowed"); + if (!stored_hash) { + _packet_hashlist.insert(packet.packet_hash()); + stored_hash = true; + } + + // TODO: Re-evaluate potential for blocking + // def send_packet(): + // Transport.transmit(const_cast(interface), packet.raw) + // thread = threading.Thread(target=send_packet) + // thread.daemon = True + // thread.start() + + transmit(const_cast(interface), packet.raw()); + sent = true; + } + else { + extreme("Transport::outbound: Packet transmission refused"); + } + } + } + } + + if (sent) { + packet.sent(true); + packet.sent_at(time(nullptr)); + + // Don't generate receipt if it has been explicitly disabled + if (packet.create_receipt() && + // Only generate receipts for DATA packets + packet.packet_type() == Packet::DATA && + // Don't generate receipts for PLAIN destinations + packet.destination().type() != Destination::PLAIN && + // Don't generate receipts for link-related packets + !(packet.context() >= Packet::KEEPALIVE && packet.context() <= Packet::LRPROOF) && + // Don't generate receipts for resource packets + !(packet.context() >= Packet::RESOURCE && packet.context() <= Packet::RESOURCE_RCL)) { + + PacketReceipt receipt(packet); + packet.receipt(receipt); + _receipts.insert(receipt); + } + + cache(packet); + } + + _jobs_locked = false; + return sent; +} + +/*static*/ bool Transport::packet_filter(const Packet &packet) { + // TODO: Think long and hard about this. + // Is it even strictly necessary with the current + // transport rules? + if (packet.context() == Packet::KEEPALIVE) { + return true; + } + if (packet.context() == Packet::RESOURCE_REQ) { + return true; + } + if (packet.context() == Packet::RESOURCE_PRF) { + return true; + } + if (packet.context() == Packet::RESOURCE) { + return true; + } + if (packet.context() == Packet::CACHE_REQUEST) { + return true; + } + if (packet.context() == Packet::CHANNEL) { + return true; + } + + if (packet.destination_type() == Destination::PLAIN) { + if (packet.packet_type() != Packet::ANNOUNCE) { + if (packet.hops() > 1) { + debug("Dropped PLAIN packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops"); + return false; + } + else { + return true; + } + } + else { + debug("Dropped invalid PLAIN announce packet"); + return false; + } + } + + if (packet.destination_type() == Destination::GROUP) { + if (packet.packet_type() != Packet::ANNOUNCE) { + if (packet.hops() > 1) { + debug("Dropped GROUP packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops"); + return false; + } + else { + return true; + } + } + else { + debug("Dropped invalid GROUP announce packet"); + return false; + } + } + + if (_packet_hashlist.find(packet.packet_hash()) == _packet_hashlist.end()) { + return true; + } + else { + if (packet.packet_type() == Packet::ANNOUNCE) { + if (packet.destination_type() == Destination::SINGLE) { + return true; + } + else { + debug("Dropped invalid announce packet"); + return false; + } + } + } + + extreme("Filtered packet with hash " + packet.packet_hash().toHex()); + return false; +} + +/*static*/ void Transport::inbound(const Bytes &raw, const Interface &interface /*= Interface::NONE*/) { + extreme("Transport::inbound()"); +/* + // If interface access codes are enabled, + // we must authenticate each packet. + //if len(raw) > 2: + if (raw.size() > 2) { + if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None: + // Check that IFAC flag is set + if raw[0] & 0x80 == 0x80: + if len(raw) > 2+interface.ifac_size: + // Extract IFAC + ifac = raw[2:2+interface.ifac_size] + + // Generate mask + mask = RNS.Cryptography.hkdf( + length=len(raw), + derive_from=ifac, + salt=interface.ifac_key, + context=None, + ) + + // Unmask payload + i = 0; unmasked_raw = b"" + for byte in raw: + if i <= 1 or i > interface.ifac_size+1: + // Unmask header bytes and payload + unmasked_raw += bytes([byte ^ mask[i]]) + else: + // Don't unmask IFAC itself + unmasked_raw += bytes([byte]) + i += 1 + raw = unmasked_raw + + // Unset IFAC flag + new_header = bytes([raw[0] & 0x7f, raw[1]]) + + // Re-assemble packet + new_raw = new_header+raw[2+interface.ifac_size:] + + // Calculate expected IFAC + expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:] + + // Check it + if ifac == expected_ifac: + raw = new_raw + else: + return + + else: + return + + else: + // If the IFAC flag is not set, but should be, + // drop the packet. + return + + else: + // If the interface does not have IFAC enabled, + // check the received packet IFAC flag. + if raw[0] & 0x80 == 0x80: + // If the flag is set, drop the packet + return + } + else { + return; + } +*/ + + while (_jobs_running) { + extreme("Transport::inbound: sleeping..."); + OS::sleep(5); + } + + if (!_identity) { + warning("Transport::inbound: No identity!"); + return; + } + + _jobs_locked = true; + + Packet packet(Destination::NONE, raw); + if (!packet.unpack()) { + warning("Transport::inbound: Pscket unpack failed!"); + return; + } + + extreme("Transport::inbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops())); + + packet.receiving_interface(interface); + packet.hops(packet.hops() + 1); + +/* + if (interface) { + if hasattr(interface, "r_stat_rssi"): + if interface.r_stat_rssi != None: + packet.rssi = interface.r_stat_rssi + if len(Transport.local_client_interfaces) > 0: + Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi]) + + while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE: + Transport.local_client_rssi_cache.pop() + + if hasattr(interface, "r_stat_snr"): + if interface.r_stat_rssi != None: + packet.snr = interface.r_stat_snr + if len(Transport.local_client_interfaces) > 0: + Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr]) + + while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE: + Transport.local_client_snr_cache.pop() + } +*/ + + if (_local_client_interfaces.size() > 0) { + if (is_local_client_interface(interface)) { + packet.hops(packet.hops() - 1); + } + } + else if (interface_to_shared_instance(interface)) { + packet.hops(packet.hops() - 1); + } + + if (packet_filter(packet)) { + extreme("Transport::inbound: Packet accepted by filter"); + _packet_hashlist.insert(packet.packet_hash()); + cache(packet); + + // Check special conditions for local clients connected + // through a shared Reticulum instance + //from_local_client = (packet.receiving_interface in Transport.local_client_interfaces) + bool from_local_client = (_local_client_interfaces.find(packet.receiving_interface()) != _local_client_interfaces.end()); + //for_local_client = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.destination_table and Transport.destination_table[packet.destination_hash][2] == 0) + //for_local_client_link = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][4] in Transport.local_client_interfaces) + //for_local_client_link |= (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][2] in Transport.local_client_interfaces) + bool for_local_client = false; + bool for_local_client_link = false; + if (packet.packet_type() != Packet::ANNOUNCE) { + auto destination_iter = _destination_table.find(packet.destination_hash()); + if (destination_iter != _destination_table.end()) { + DestinationEntry destination_entry = (*destination_iter).second; + if (destination_entry._hops == 0) { + for_local_client = true; + } + } + auto link_iter = _link_table.find(packet.destination_hash()); + if (link_iter != _link_table.end()) { + LinkEntry link_entry = (*link_iter).second; + if (_local_client_interfaces.find(link_entry._receiving_interface) != _local_client_interfaces.end()) { + for_local_client_link = true; + } + if (_local_client_interfaces.find(link_entry._outbound_interface) != _local_client_interfaces.end()) { + for_local_client_link = true; + } + } + } + //proof_for_local_client = (packet.destination_hash in Transport.reverse_table) and (Transport.reverse_table[packet.destination_hash][0] in Transport.local_client_interfaces) + bool proof_for_local_client = false; + auto reverse_iter = _reverse_table.find(packet.destination_hash()); + if (reverse_iter != _reverse_table.end()) { + ReverseEntry reverse_entry = (*reverse_iter).second; + if (_local_client_interfaces.find(reverse_entry._receiving_interface) != _local_client_interfaces.end()) { + proof_for_local_client = true; + } + } + + // Plain broadcast packets from local clients are sent + // directly on all attached interfaces, since they are + // never injected into transport. + if (_control_hashes.find(packet.destination_hash()) == _control_hashes.end()) { + if (packet.destination_type() == Destination::PLAIN && packet.transport_type() == Transport::BROADCAST) { + // Send to all interfaces except the originator + if (from_local_client) { + for (const Interface &interface : _interfaces) { + if (interface != packet.receiving_interface()) { + extreme("Transport::inbound: Broadcasting packet on " + interface.toString()); + transmit(const_cast(interface), packet.raw()); + } + } + } + // If the packet was not from a local client, send + // it directly to all local clients + else { + for (auto &interface : _local_client_interfaces) { + extreme("Transport::inbound: Broadcasting packet on " + interface.toString()); + transmit(const_cast(interface), packet.raw()); + } + } + } + } + + // General transport handling. Takes care of directing + // packets according to transport tables and recording + // entries in reverse and link tables. + if (Reticulum::transport_enabled() || from_local_client or for_local_client or for_local_client_link) { + extreme("Transport::inbound: Performing general transport handling"); + + // If there is no transport id, but the packet is + // for a local client, we generate the transport + // id (it was stripped on the previous hop, since + // we "spoof" the hop count for clients behind a + // shared instance, so they look directly reach- + // able), and reinsert, so the normal transport + // implementation can handle the packet. + if (!packet.transport_id() && for_local_client) { + packet.transport_id(_identity.hash()); + } + + // If this is a cache request, and we can fullfill + // it, do so and stop processing. Otherwise resume + // normal processing. + if (packet.context() == Packet::CACHE_REQUEST) { + if (cache_request_packet(packet)) { + extreme("Transport::inbound: Cached packet"); + return; + } + } + + // If the packet is in transport, check whether we + // are the designated next hop, and process it + // accordingly if we are. + if (packet.transport_id() && packet.packet_type() != Packet::ANNOUNCE) { + if (packet.transport_id() == _identity.hash()) { + auto destination_iter = _destination_table.find(packet.destination_hash()); + if (destination_iter != _destination_table.end()) { + DestinationEntry destination_entry = (*destination_iter).second; + Bytes next_hop = destination_entry._received_from; + uint8_t remaining_hops = destination_entry._hops; + + Bytes new_raw; + if (remaining_hops > 1) { + // Just increase hop count and transmit + //new_raw = packet.raw[0:1] + new_raw << packet.raw().left(1); + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += next_hop + new_raw << next_hop; + //new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:] + new_raw << packet.raw().mid((Identity::TRUNCATED_HASHLENGTH/8)+2); + } + else if (remaining_hops == 1) { + // Strip transport headers and transmit + //new_flags = (RNS.Packet.HEADER_1) << 6 | (Transport.BROADCAST) << 4 | (packet.flags & 0b00001111) + uint8_t new_flags = (Packet::HEADER_1) << 6 | (Transport::BROADCAST) << 4 | (packet.flags() & 0b00001111); + //new_raw = struct.pack("!B", new_flags) + new_raw << new_flags; + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:] + new_raw << packet.raw().mid((Identity::TRUNCATED_HASHLENGTH/8)+2); + } + else if (remaining_hops == 0) { + // Just increase hop count and transmit + //new_raw = packet.raw[0:1] + new_raw << packet.raw().left(1); + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + } + + Interface &outbound_interface = destination_entry._receiving_interface; + + if (packet.packet_type() == Packet::LINKREQUEST) { + uint64_t now = OS::time(); + uint64_t proof_timeout = now + Link::ESTABLISHMENT_TIMEOUT_PER_HOP*1000 * std::max((uint8_t)1, remaining_hops); + LinkEntry link_entry( + now, + next_hop, + outbound_interface, + remaining_hops, + packet.receiving_interface(), + packet.hops(), + packet.destination_hash(), + false, + proof_timeout + ); + _link_table.insert({packet.getTruncatedHash(), link_entry}); + } + else { + ReverseEntry reverse_entry( + packet.receiving_interface(), + outbound_interface, + OS::time() + ); + _reverse_table.insert({packet.getTruncatedHash(), reverse_entry}); + } + transmit(const_cast(outbound_interface), new_raw); + destination_entry._timestamp = OS::time(); + } + else { + // TODO: There should probably be some kind of REJECT + // mechanism here, to signal to the source that their + // expected path failed. + extreme("Got packet in transport, but no known path to final destination " + packet.destination_hash().toHex() + ". Dropping packet."); + } + } + } + + // Link transport handling. Directs packets according + // to entries in the link tables + if (packet.packet_type() != Packet::ANNOUNCE && packet.packet_type() != Packet::LINKREQUEST && packet.context() == Packet::LRPROOF) { + auto link_iter = _link_table.find(packet.destination_hash()); + if (link_iter != _link_table.end()) { + LinkEntry link_entry = (*link_iter).second; + // If receiving and outbound interface is + // the same for this link, direction doesn't + // matter, and we simply send the packet on. + Interface outbound_interface(Interface::NONE); + if (link_entry._outbound_interface == link_entry._receiving_interface) { + // But check that taken hops matches one + // of the expectede values. + if (packet.hops() == link_entry._remaining_hops || packet.hops() == link_entry._hops) { + outbound_interface = link_entry._outbound_interface; + } + } + else { + // If interfaces differ, we transmit on + // the opposite interface of what the + // packet was received on. + if (packet.receiving_interface() == link_entry._outbound_interface) { + // Also check that expected hop count matches + if (packet.hops() == link_entry._remaining_hops) { + outbound_interface = link_entry._receiving_interface; + } + } + else if (packet.receiving_interface() == link_entry._receiving_interface) { + // Also check that expected hop count matches + if (packet.hops() == link_entry._hops) { + outbound_interface = link_entry._outbound_interface; + } + } + } + + if (outbound_interface) { + Bytes new_raw; + //new_raw = packet.raw[0:1] + new_raw << packet.raw().left(1); + //new_raw += struct.pack("!B", packet.hops) + new_raw << packet.hops(); + //new_raw += packet.raw[2:] + new_raw << packet.raw().mid(2); + transmit(outbound_interface, new_raw); + link_entry._timestamp = OS::time(); + } + else { + // future + } + } + } + } + + // Announce handling. Handles logic related to incoming + // announces, queueing rebroadcasts of these, and removal + // of queued announce rebroadcasts once handed to the next node. + if (packet.packet_type() == Packet::ANNOUNCE) { + extreme("Transport::inbound: Packet is ANNOUNCE"); + Bytes received_from; + //p local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) + //Destination local_destination(Destination::NONE); + bool found_local = false; + for (auto &destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + //local_destination = destination; + found_local = true; + break; + } + } + //if local_destination == None and RNS.Identity.validate_announce(packet): + //if (!local_destination && Identity::validate_announce(packet)) { + if (!found_local && Identity::validate_announce(packet)) { + extreme("Transport::inbound: Packet is announce for non-local destination, processing..."); + if (packet.transport_id()) { + received_from = packet.transport_id(); + + // Check if this is a next retransmission from + // another node. If it is, we're removing the + // announce in question from our pending table + if (Reticulum::transport_enabled() && _announce_table.count(packet.destination_hash()) > 0) { + //AnnounceEntry &announce_entry = _announce_table[packet.destination_hash()]; + AnnounceEntry &announce_entry = (*_announce_table.find(packet.destination_hash())).second; + + if ((packet.hops() - 1) == announce_entry._hops) { + debug("Heard a local rebroadcast of announce for " + packet.destination_hash().toHex()); + announce_entry._local_rebroadcasts += 1; + if (announce_entry._local_rebroadcasts >= Transport::LOCAL_REBROADCASTS_MAX) { + debug("Max local rebroadcasts of announce for " + packet.destination_hash().toHex() + " reached, dropping announce from our table"); + _announce_table.erase(packet.destination_hash()); + } + } + + if ((packet.hops() - 1) == (announce_entry._hops + 1) && announce_entry._retries > 0) { + uint64_t now = OS::time(); + if (now < announce_entry._timestamp) { + debug("Rebroadcasted announce for " + packet.destination_hash().toHex() + " has been passed on to another node, no further tries needed"); + _announce_table.erase(packet.destination_hash()); + } + } + } + } + else { + received_from = packet.destination_hash(); + } + + // Check if this announce should be inserted into + // announce and destination tables + bool should_add = false; + + // First, check that the announce is not for a destination + // local to this system, and that hops are less than the max + //if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1): + bool found_local = false; + for (auto &destination : _destinations) { + if (destination.hash() == packet.destination_hash()) { + found_local = true; + break; + } + } + if (!found_local && packet.hops() < (Transport::PATHFINDER_M+1)) { + extreme("Transport::inbound: Packet is announce for non-local destination, processing..."); +/* + uint64_t announce_emitted = Transport::announce_emitted(packet); + + //prandom_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10] + Bytes random_blob = packet.data().mid(Identity::KEYSIZE/8 + Identity::NAME_HASH_LENGTH/8, 10); + //prandom_blobs = [] + auto iter = _destination_table.find(packet.destination_hash()); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + //prandom_blobs = Transport.destination_table[packet.destination_hash][4] + + // If we already have a path to the announced + // destination, but the hop count is equal or + // less, we'll update our tables. + if (packet.hops() <= destination_entry._hops) { + // Make sure we haven't heard the random + // blob before, so announces can't be + // replayed to forge paths. + // TODO: Check whether this approach works + // under all circumstances + //pif not random_blob in random_blobs: + if (destination_entry._random_blobs.find(random_blob) == destination_entry._random_blobs.end()) { + should_add = true; + } + else { + should_add = false; + } + } + else { + // If an announce arrives with a larger hop + // count than we already have in the table, + // ignore it, unless the path is expired, or + // the emission timestamp is more recent. + uint64_t now = OS::time(); + uint64_t path_expires = destination_entry._expires; + + uint64_t path_announce_emitted = 0; + for (const Bytes &path_random_blob : destination_entry._random_blobs) { + //path_announce_emitted = std::max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big")) + //zpath_announce_emitted = std::max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big")) + if (path_announce_emitted >= announce_emitted) { + break; + } + } + + if (now >= path_expires) { + // We also check that the announce is + // different from ones we've already heard, + // to avoid loops in the network + if (destination_entry._random_blobs.find(random_blob) == destination_entry._random_blobs.end()) { + // TODO: Check that this ^ approach actually + // works under all circumstances + debug("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce due to expired path"); + should_add = true; + } + else { + should_add = false; + } + } + else { + if (announce_emitted > path_announce_emitted) { + if (destination_entry._random_blobs.find(random_blob) == destination_entry._random_blobs.end()) { + debug("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce, since it was more recently emitted"); + should_add = true; + } + else { + should_add = false; + } + } + } + } + } + + else { + // If this destination is unknown in our table + // we should add it + should_add = true; + } + + if (should_add) { + uint64_t now = OS::time(); + + bool rate_blocked = false; + + + if packet.context != RNS.Packet.PATH_RESPONSE and packet.receiving_interface.announce_rate_target != None: + if not packet.destination_hash in Transport.announce_rate_table: + rate_entry = { "last": now, "rate_violations": 0, "blocked_until": 0, "timestamps": [now]} + Transport.announce_rate_table[packet.destination_hash] = rate_entry + + else: + rate_entry = Transport.announce_rate_table[packet.destination_hash] + rate_entry["timestamps"].append(now) + + while len(rate_entry["timestamps"]) > Transport.MAX_RATE_TIMESTAMPS: + rate_entry["timestamps"].pop(0) + + current_rate = now - rate_entry["last"] + + if now > rate_entry["blocked_until"]: + + if current_rate < packet.receiving_interface.announce_rate_target: + rate_entry["rate_violations"] += 1 + + else: + rate_entry["rate_violations"] = std::max(0, rate_entry["rate_violations"]-1) + + if rate_entry["rate_violations"] > packet.receiving_interface.announce_rate_grace: + rate_target = packet.receiving_interface.announce_rate_target + rate_penalty = packet.receiving_interface.announce_rate_penalty + rate_entry["blocked_until"] = rate_entry["last"] + rate_target + rate_penalty + rate_blocked = True + else: + rate_entry["last"] = now + + else: + rate_blocked = True + + + uint8_t retries = 0; + uint8_t announce_hops = packet.hops(); + uint8_t local_rebroadcasts = 0; + bool block_rebroadcasts = false; + Interface attached_interface = Interface::NONE; + + uint64_t retransmit_timeout = now + (RNS::Cryptography::random() * Transport::PATHFINDER_RW); + + uint64_t expires; + if (packet.receiving_interface().mode() == Interface::MODE_ACCESS_POINT) { + expires = now + Transport::AP_PATH_TIME; + } + else if (packet.receiving_interface().mode() == Interface::MODE_ROAMING) { + expires = now + Transport::ROAMING_PATH_TIME; + } + else { + expires = now + Transport::PATHFINDER_E; + } + + std::set random_blobs; + random_blobs.insert(random_blob); + + if (Reticulum::transport_enabled() || from_local_client(packet) && packet.context() != Packet::PATH_RESPONSE) { + // Insert announce into announce table for retransmission + + if (rate_blocked) { + debug("Blocking rebroadcast of announce from " + packet.destination_hash().toHex() + " due to excessive announce rate"); + } + else { + if (from_local_client(packet)) { + // If the announce is from a local client, + // it is announced immediately, but only one time. + retransmit_timeout = now; + retries = Transport::PATHFINDER_R; + } + _announce_table[packet.destination_hash] = [ + now, + retransmit_timeout, + retries, + received_from, + announce_hops, + packet, + local_rebroadcasts, + block_rebroadcasts, + attached_interface + ]; + } + } + // TODO: Check from_local_client once and store result + else if (from_local_client(packet) && packet.context() == Packet::PATH_RESPONSE) { + // If this is a path response from a local client, + // check if any external interfaces have pending + // path requests. + //pif packet.destination_hash in Transport.pending_local_path_requests: + auto iter = pending_local_path_requests.find(packet.destination_hash()); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + desiring_interface = _pending_local_path_requests.pop(packet.destination_hash()); + retransmit_timeout = now; + retries = Transport::PATHFINDER_R; + + Transport.announce_table[packet.destination_hash] = [ + now, + retransmit_timeout, + retries, + received_from, + announce_hops, + packet, + local_rebroadcasts, + block_rebroadcasts, + attached_interface + ]; + + // If we have any local clients connected, we re- + // transmit the announce to them immediately + if (_local_client_interfaces.size() > 0) { + announce_identity = Identity::recall(packet.destination_hash()); + announce_destination = Destination(announce_identity, Destination.OUT, Destination.SINGLE, "unknown", "unknown"); + announce_destination.hash(packet.destination_hash()); + announce_destination.hexhash = announce_destination.hash().toHex(); + announce_context = Packet::NONE; + announce_data = packet.data(); + + // TODO: Shouldn't the context be PATH_RESPONSE in the first case here? + if (from_local_client(packet) && packet.context() == Packet.PATH_RESPONSE) { + for (auto &local_interface : _local_client_interfaces) { + if packet.receiving_interface() != local_interface) { + Packet new_announce( + announce_destination, + announce_data, + Packet::ANNOUNCE, + context = announce_context, + header_type = RNS.Packet.HEADER_2, + transport_type = Transport.TRANSPORT, + transport_id = Transport.identity.hash, + attached_interface = local_interface + ); + + new_announce.hops(packet.hops); + new_announce.send(); + } + } + } + else { + for (auto &local_interface : _local_client_interfaces) { + if (packet.receiving_interface() != local_interface) { + Packet new_announce( + announce_destination, + announce_data, + Packet::ANNOUNCE, + context = announce_context, + header_type = RNS.Packet.HEADER_2, + transport_type = Transport.TRANSPORT, + transport_id = Transport.identity.hash, + attached_interface = local_interface + ); + + new_announce.hops(packet.hops()); + new_announce.send(); + } + } + } + } + + // If we have any waiting discovery path requests + // for this destination, we retransmit to that + // interface immediately + auto iter = _discovery_path_requests.find(packet.destination_hash()); + if (iter != _discovery_path_requests.end()) { + PathRequestEntry pr_entry = (*iter).second; + attached_interface = pr_entry._requesting_interface; + + interface_str = " on " + attached_interface.toString(); + + debug("Got matching announce, answering waiting discovery path request for " + packet.destination_hash().toHex() + interface_str); + announce_identity = Identity::recall(packet.destination_hash()); + Destination announce_destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown"); + announce_destination.hash(packet.destination_hash()); + announce_destination.hexhash = announce_destination.hash().toHex(); + announce_context = Packet::NONE; + announce_data = packet.data(); + + Packet new_announce( + announce_destination, + announce_data, + Packet::ANNOUNCE, + context = Packet::PATH_RESPONSE, + header_type = Packet::HEADER_2, + transport_type = Transport::TRANSPORT, + transport_id = _identity.hash(), + attached_interface = attached_interface + ); + + new_announce.hops(packet.hops()); + new_announce.send(); + } + + DestinationEntry destination_table_entry( + now, + received_from, + announce_hops, + expires, + random_blobs, + packet.receiving_interface(), + packet + ); + _destination_table.insert({packet.destination_hash(), destination_table_entry}); + debug("Destination " + packet.destination_hash().toHex() + " is now " + std::to_string(announce_hops) + " hops away via " + received_from.toHex() + " on " + packet.receiving_interface().toString()); + + // If the receiving interface is a tunnel, we add the + // announce to the tunnels table + if (packet.receiving_interface().tunnel_id()) { + tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]; + paths = tunnel_entry[2] + paths[packet.destination_hash] = destination_table_entry + expires = OS::time() + Transport::DESTINATION_TIMEOUT; + tunnel_entry[3] = expires + debug("Path to " + packet.destination_hash().toHex() + " associated with tunnel " + packet.receiving_interface().tunnel_id().toHex()); + } + + // Call externally registered callbacks from apps + // wanting to know when an announce arrives + if (packet.context() != Packet::PATH_RESPONSE) { + for (auto &handler : Transport.announce_handlers) { + try { + // Check that the announced destination matches + // the handlers aspect filter + execute_callback = false; + announce_identity = Identity::recall(packet.destination_hash()); + if (handler.aspect_filter == None) { + // If the handlers aspect filter is set to + // None, we execute the callback in all cases + execute_callback = true; + } + else { + handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity) + if (packet.destination_hash() == handler_expected_hash() ) { + execute_callback = true; + } + } + if (execute_callback) { + handler.received_announce( + destination_hash=packet.destination_hash(), + announced_identity=announce_identity, + app_data=Identity::recall_app_data(packet.destination_hash()) + ); + } + } + catch (std::exception &e) { + error("Error while processing external announce callback."); + error("The contained exception was: " + e.what(); + } + } + } + } +*/ + } + else { + extreme("Transport::inbound: Packet is announce for local destination, not processing"); + } + } + else { + extreme("Transport::inbound: Packet is announce for local destination, not processing"); + } + } + + // Handling for link requests to local destinations + else if (packet.packet_type() == Packet::LINKREQUEST) { + extreme("Transport::inbound: Packet is LINKREQUEST"); + if (!packet.transport_id() || packet.transport_id() == _identity.hash()) { + for (auto &destination : _destinations) { + if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) { + packet.destination(destination); + // CBA iterator over std::set is always const so need to make temporarily mutable + //destination.receive(packet); + const_cast(destination).receive(packet); + } + } + } + } + + // Handling for local data packets + else if (packet.packet_type() == Packet::DATA) { + extreme("Transport::inbound: Packet is DATA"); + if (packet.destination_type() == Destination::LINK) { + for (auto &link : _active_links) { + if (link.link_id() == packet.destination_hash()) { + packet.link(link); + const_cast(link).receive(packet); + } + } + } + else { + for (auto &destination : _destinations) { + if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) { + packet.destination(destination); + const_cast(destination).receive(packet); + + if (destination.proof_strategy() == Destination::PROVE_ALL) { + packet.prove(); + } + else if (destination.proof_strategy() == Destination::PROVE_APP) { + if (destination.callbacks()._proof_requested) { + try { + if (destination.callbacks()._proof_requested(packet)) { + packet.prove(); + } + } + catch (std::exception &e) { + error(std::string("Error while executing proof request callback. The contained exception was: ") + e.what()); + } + } + } + } + } + } + } + + // Handling for proofs and link-request proofs + else if (packet.packet_type() == Packet::PROOF) { + extreme("Transport::inbound: Packet is PROOF"); +/* + if packet.context == RNS.Packet.LRPROOF: + // This is a link request proof, check if it + // needs to be transported + if (RNS.Reticulum.transport_enabled() or for_local_client_link or from_local_client) and packet.destination_hash in Transport.link_table: + link_entry = Transport.link_table[packet.destination_hash] + if packet.receiving_interface == link_entry[2]: + try: + if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2: + peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2] + peer_identity = RNS.Identity.recall(link_entry[6]) + peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE] + + signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes + signature = packet.data[:RNS.Identity.SIGLENGTH//8] + + if peer_identity.validate(signature, signed_data): + RNS.log("Link request proof validated for transport via "+str(link_entry[4]), RNS.LOG_EXTREME) + new_raw = packet.raw[0:1] + new_raw += struct.pack("!B", packet.hops) + new_raw += packet.raw[2:] + Transport.link_table[packet.destination_hash][7] = True + Transport.transmit(link_entry[4], new_raw) + + else: + RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) + + except Exception as e: + RNS.log("Error while transporting link request proof. The contained exception was: "+str(e), RNS.LOG_ERROR) + + else: + RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) + else: + // Check if we can deliver it to a local + // pending link + for link in Transport.pending_links: + if link.link_id == packet.destination_hash: + link.validate_proof(packet) + + elif packet.context == RNS.Packet.RESOURCE_PRF: + for link in Transport.active_links: + if link.link_id == packet.destination_hash: + link.receive(packet) + else: + if packet.destination_type == RNS.Destination.LINK: + for link in Transport.active_links: + if link.link_id == packet.destination_hash: + packet.link = link + + if len(packet.data) == RNS.PacketReceipt.EXPL_LENGTH: + proof_hash = packet.data[:RNS.Identity.HASHLENGTH//8] + else: + proof_hash = None + + // Check if this proof neds to be transported + if (RNS.Reticulum.transport_enabled() or from_local_client or proof_for_local_client) and packet.destination_hash in Transport.reverse_table: + reverse_entry = Transport.reverse_table.pop(packet.destination_hash) + if packet.receiving_interface == reverse_entry[1]: + RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_EXTREME) + new_raw = packet.raw[0:1] + new_raw += struct.pack("!B", packet.hops) + new_raw += packet.raw[2:] + Transport.transmit(reverse_entry[0], new_raw) + else: + RNS.log("Proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) + + for receipt in Transport.receipts: + receipt_validated = False + if proof_hash != None: + // Only test validation if hash matches + if receipt.hash == proof_hash: + receipt_validated = receipt.validate_proof_packet(packet) + else: + // TODO: This looks like it should actually + // be rewritten when implicit proofs are added. + + // In case of an implicit proof, we have + // to check every single outstanding receipt + receipt_validated = receipt.validate_proof_packet(packet) + + if receipt_validated: + if receipt in Transport.receipts: + Transport.receipts.remove(receipt) +*/ + } + } + + _jobs_locked = false; +} + +/*static*/ void Transport::synthesize_tunnel(const Interface &interface) { +/* + Bytes interface_hash = interface.get_hash(); + Bytes public_key = _identity.get_public_key(); + Bytes random_hash = Identity::get_random_hash(); + + tunnel_id_data = public_key+interface_hash + tunnel_id = RNS.Identity.full_hash(tunnel_id_data) + + signed_data = tunnel_id_data+random_hash + signature = Transport.identity.sign(signed_data) + + data = signed_data+signature + + tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize") + + packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface) + packet.send() + + interface.wants_tunnel = False +*/ +} + +/*static*/ void Transport::tunnel_synthesize_handler(const Bytes &data, const Packet &packet) { +/* + try: + expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8 + if len(data) == expected_length: + public_key = data[:RNS.Identity.KEYSIZE//8] + interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8] + tunnel_id_data = public_key+interface_hash + tunnel_id = RNS.Identity.full_hash(tunnel_id_data) + random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8] + + signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length] + signed_data = tunnel_id_data+random_hash + + remote_transport_identity = RNS.Identity(create_keys=False) + remote_transport_identity.load_public_key(public_key) + + if remote_transport_identity.validate(signature, signed_data): + Transport.handle_tunnel(tunnel_id, packet.receiving_interface) + + except Exception as e: + RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG) + RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG) +*/ +} + +/*static*/ void Transport::handle_tunnel(const Bytes &tunnel_id, const Interface &interface) { +/* + expires = time.time() + Transport.DESTINATION_TIMEOUT + if not tunnel_id in Transport.tunnels: + RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG) + paths = {} + tunnel_entry = [tunnel_id, interface, paths, expires] + interface.tunnel_id = tunnel_id + Transport.tunnels[tunnel_id] = tunnel_entry + else: + RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG) + tunnel_entry = Transport.tunnels[tunnel_id] + tunnel_entry[1] = interface + tunnel_entry[3] = expires + interface.tunnel_id = tunnel_id + paths = tunnel_entry[2] + + deprecated_paths = [] + for destination_hash, path_entry in paths.items(): + received_from = path_entry[1] + announce_hops = path_entry[2] + expires = path_entry[3] + random_blobs = path_entry[4] + receiving_interface = interface + packet = path_entry[6] + new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet] + + should_add = False + if destination_hash in Transport.destination_table: + old_entry = Transport.destination_table[destination_hash] + old_hops = old_entry[2] + old_expires = old_entry[3] + if announce_hops <= old_hops or time.time() > old_expires: + should_add = True + else: + RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG) + else: + if time.time() < expires: + should_add = True + else: + RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because it has expired", RNS.LOG_DEBUG) + + if should_add: + Transport.destination_table[destination_hash] = new_entry + RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG) + else: + deprecated_paths.append(destination_hash) + + for deprecated_path in deprecated_paths: + RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG) + paths.pop(deprecated_path) +*/ +} + +/*static*/ void Transport::register_interface(Interface &interface) { + extreme("Transport: Registering interface " + interface.toString()); + _interfaces.push_back(interface); + extreme("Transport: Listing all registered interfaces..."); + for (Interface &found_interface : _interfaces) { + extreme("Transport: Found interface " + found_interface.toString()); + } + // CBA TODO set or add transport as listener on interface to receive incoming packets +} + +/*static*/ void Transport::deregister_interface(const Interface &interface) { + extreme("Transport: Deregistering interface " + interface.toString()); + //if (_interfaces.find(interface) != _interfaces.end()) { + // _interfaces.erase(interface); + //} + for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) { + if ((*iter).get() == interface) { + _interfaces.erase(iter); + break; + } + } +} /*static*/ void Transport::register_destination(Destination &destination) { + extreme("Transport: Registering destination " + destination.toString()); 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.") + if (destination.direction() == Destination::IN) { + for (auto ®istered_destination : _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); + _destinations.insert(destination); - if (Transport.owner.is_connected_to_shared_instance) { - if (destination->type == Destination::SINGLE) { - destination->announce(path_response=True); + if (_owner.is_connected_to_shared_instance()) { + if (destination.type() == Destination::SINGLE) { + destination.announce({}, true); } } } +} + +/*static*/ void Transport::deregister_destination(const Destination &destination) { + extreme("Transport: Deregistering destination " + destination.toString()); + if (_destinations.find(destination) != _destinations.end()) { + _destinations.erase(destination); + } +} + +/*static*/ void Transport::register_link(const Link &link) { +/* + extreme("Transport: Registering link " + link.toString()); + if (link.initiator()) { + _pending_links.insert(link); + } + else { + _active_links.insert(link); + } +*/ +} + +/*static*/ void Transport::activate_link(Link &link) { +/* + extreme("Transport: Activating link " + link.toString()); + if (_pending_links.find(link) != _pending_links.end()) { + if (link.status() != Link::ACTIVE) { + throw std::runtime_error("Invalid link state for link activation: " + link.status_string()); + } + _pending_links.erase(link); + _active_links.insert(link); + link.status(Link::ACTIVE); + } + else { + error("Attempted to activate a link that was not in the pending table"); + } +*/ +} + +/* +Registers an announce handler. + +:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example` for more info. +*/ +/*static*/ void Transport::register_announce_handler(HAnnounceHandler handler) { + extreme("Transport: Transport::register_announce_handler()"); + //if hasattr(handler, "received_announce") and callable(handler.received_announce): + //if hasattr(handler, "aspect_filter"): + _announce_handlers.insert(handler); +} + +/* +Deregisters an announce handler. + +:param handler: The announce handler to be deregistered. +*/ +/*static*/ void Transport::deregister_announce_handler(HAnnounceHandler handler) { + extreme("Transport: Transport::deregister_announce_handler()"); + if (_announce_handlers.find(handler) != _announce_handlers.end()) { + _announce_handlers.erase(handler); + } +} + +/*static*/ Interface Transport::find_interface_from_hash(const Bytes &interface_hash) { + for (const Interface &interface : _interfaces) { + if (interface.get_hash() == interface_hash) { + return interface; + } + } + + return {Interface::NONE}; +} + +/*static*/ bool Transport::should_cache(const Packet &packet) { + // TODO: Rework the caching system. It's currently + // not very useful to even cache Resource proofs, + // disabling it for now, until redesigned. + // if packet.context == RNS.Packet.RESOURCE_PRF: + // return True + + return false; +} + +// When caching packets to storage, they are written +// exactly as they arrived over their interface. This +// means that they have not had their hop count +// increased yet! Take note of this when reading from +// the packet cache. +/*static*/ void Transport::cache(const Packet &packet, bool force_cache /*= false*/) { +/* + if (should_cache(packet) || force_cache) { + try { + //packet_hash = RNS.hexrep(packet.get_hash(), delimit=False) + Bytes packet_hash = packet.get_hash().toHex(); + //interface_reference = None + std::string interface_reference; + if (packet.receiving_interface()) { + interface_reference = packet.receiving_interface().toString(); + } + + file = open(RNS.Reticulum.cachepath+"/"+packet_hash, "wb") + file.write(umsgpack.packb([packet.raw, interface_reference])) + file.close() + } + catch (std::exception &e) { + error("Error writing packet to cache. The contained exception was: " + e.what()); + } + } */ } -/*static*/ bool Transport::outbound(const Packet &packet) { +/*static*/ Packet Transport::get_cached_packet(const Bytes &packet_hash) { +/* + try { + //packet_hash = RNS.hexrep(packet_hash, delimit=False) + Bytes packet_hash = packet_hash.toHex(); + path = RNS.Reticulum.cachepath+"/"+packet_hash + + if os.path.isfile(path): + file = open(path, "rb") + cached_data = umsgpack.unpackb(file.read()) + file.close() + + packet = RNS.Packet(None, cached_data[0]) + interface_reference = cached_data[1] + + for interface in Transport.interfaces: + if str(interface) == interface_reference: + packet.receiving_interface = interface + + return packet + else: + return None + } + catch (std::exception &e) { + error("Exception occurred while getting cached packet."); + error("The contained exception was: " + e.what()); + } +*/ // MOCK - return true; + return {Packet::NONE}; +} + +/*static*/ bool Transport::cache_request_packet(const Packet &packet) { + if (packet.data().size() == Identity::HASHLENGTH/8) { + const Packet &cached_packet = get_cached_packet(packet.data()); + + if (cached_packet) { + // If the packet was retrieved from the local + // cache, replay it to the Transport instance, + // so that it can be directed towards it original + // destination. + inbound(cached_packet.raw(), cached_packet.receiving_interface()); + return true; + } + else { + return false; + } + } + else { + return false; + } +} + +/*static*/ void Transport::cache_request(const Bytes &packet_hash, const Destination &destination) { + const Packet &cached_packet = get_cached_packet(packet_hash); + if (cached_packet) { + // The packet was found in the local cache, + // replay it to the Transport instance. + inbound(cached_packet.raw(), cached_packet.receiving_interface()); + } + else { + // The packet is not in the local cache, + // query the network. + Packet request(destination, packet_hash, Packet::DATA, Packet::CACHE_REQUEST); + request.send(); + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: *True* if a path to the destination is known, otherwise *False*. +*/ +/*static*/ bool Transport::has_path(const Bytes &destination_hash) { + if (_destination_table.find(destination_hash) != _destination_table.end()) { + return true; + } + else { + return false; + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown. +*/ +/*static*/ uint8_t Transport::hops_to(const Bytes &destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + return destination_entry._hops; + } + else { + return Transport::PATHFINDER_M; + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown. +*/ +/*static*/ Bytes Transport::next_hop(const Bytes &destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + return destination_entry._received_from; + } + else { + return {}; + } +} + +/* +:param destination_hash: A destination hash as *bytes*. +:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown. +*/ +/*static*/ Interface Transport::next_hop_interface(const Bytes &destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + return destination_entry._receiving_interface; + } + else { + return {Interface::NONE}; + } +} + +/*static*/ bool Transport::expire_path(const Bytes &destination_hash) { + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + DestinationEntry destination_entry = (*iter).second; + destination_entry._timestamp = 0; + _tables_last_culled = 0; + return true; + } + else { + return false; + } +} + +/* +Requests a path to the destination from the network. If +another reachable peer on the network knows a path, it +will announce it. + +:param destination_hash: A destination hash as *bytes*. +:param on_interface: If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used. +*/ +/*static*/ void Transport::request_path(const Bytes &destination_hash, const Interface &on_interface /*= {Interface::NONE}*/, const Bytes &tag /*= {}*/, bool recursive /*= false*/) { +/* + if tag == None: + request_tag = RNS.Identity.get_random_hash() + else: + request_tag = tag + + if RNS.Reticulum.transport_enabled(): + path_request_data = destination_hash+Transport.identity.hash+request_tag + else: + path_request_data = destination_hash+request_tag + + path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") + packet = RNS.Packet(path_request_dst, path_request_data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = on_interface) + + if on_interface != None and recursive: + if not hasattr(on_interface, "announce_cap"): + on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP + + if not hasattr(on_interface, "announce_allowed_at"): + on_interface.announce_allowed_at = 0 + + if not hasattr(on_interface, "announce_queue"): + on_interface.announce_queue = [] + + queued_announces = True if len(on_interface.announce_queue) > 0 else False + if queued_announces: + RNS.log("Blocking recursive path request on "+str(on_interface)+" due to queued announces", RNS.LOG_EXTREME) + return + else: + now = time.time() + if now < on_interface.announce_allowed_at: + RNS.log("Blocking recursive path request on "+str(on_interface)+" due to active announce cap", RNS.LOG_EXTREME) + return + else: + tx_time = ((len(path_request_data)+RNS.Reticulum.HEADER_MINSIZE)*8) / on_interface.bitrate + wait_time = (tx_time / on_interface.announce_cap) + on_interface.announce_allowed_at = now + wait_time + + packet.send() + Transport.path_requests[destination_hash] = time.time() +*/ +} + +/*static*/ void Transport::path_request_handler(const Bytes &data, const Packet &packet) { +/* + try: + // If there is at least bytes enough for a destination + // hash in the packet, we assume those bytes are the + // destination being requested. + if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8: + destination_hash = data[:RNS.Identity.TRUNCATED_HASHLENGTH//8] + // If there is also enough bytes for a transport + // instance ID and at least one tag byte, we + // assume the next bytes to be the trasport ID + // of the requesting transport instance. + if len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8)*2: + requesting_transport_instance = data[RNS.Identity.TRUNCATED_HASHLENGTH//8:(RNS.Identity.TRUNCATED_HASHLENGTH//8)*2] + else: + requesting_transport_instance = None + + tag_bytes = None + if len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8)*2: + tag_bytes = data[RNS.Identity.TRUNCATED_HASHLENGTH//8*2:] + + elif len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8): + tag_bytes = data[RNS.Identity.TRUNCATED_HASHLENGTH//8:] + + if tag_bytes != None: + if len(tag_bytes) > RNS.Identity.TRUNCATED_HASHLENGTH//8: + tag_bytes = tag_bytes[:RNS.Identity.TRUNCATED_HASHLENGTH//8] + + unique_tag = destination_hash+tag_bytes + + if not unique_tag in Transport.discovery_pr_tags: + Transport.discovery_pr_tags.append(unique_tag) + + Transport.path_request( + destination_hash, + Transport.from_local_client(packet), + packet.receiving_interface, + requestor_transport_id = requesting_transport_instance, + tag=tag_bytes + ) + + else: + RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_DEBUG) + + else: + RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) + + except Exception as e: + RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR) +*/ +} + +/*static*/ void Transport::path_request(const Bytes &destination_hash, bool is_from_local_client, const Interface &attached_interface, const Bytes &requestor_transport_id /*= {}*/, const Bytes &tag /*= {}*/) { +/* + should_search_for_unknown = False + + if attached_interface != None: + if RNS.Reticulum.transport_enabled() and attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR: + should_search_for_unknown = True + + interface_str = " on "+str(attached_interface) + else: + interface_str = "" + + RNS.log("Path request for "+RNS.prettyhexrep(destination_hash)+interface_str, RNS.LOG_DEBUG) + + destination_exists_on_local_client = False + if len(Transport.local_client_interfaces) > 0: + if destination_hash in Transport.destination_table: + destination_interface = Transport.destination_table[destination_hash][5] + + if Transport.is_local_client_interface(destination_interface): + destination_exists_on_local_client = True + Transport.pending_local_path_requests[destination_hash] = attached_interface + + //local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None) + Destination local_destination(Destination::NONE); + for (auto &destination : _destinations) { + if (destination.hash() == destination_hash) { + local_destination = destination; + break; + } + } + //if local_destination != None: + if (local_destination) { + local_destination.announce(path_response=True, tag=tag, attached_interface=attached_interface); + debug("Answering path request for " + destination_hash.toHex() + interface_str + ", destination is local to this system"); + } + + elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table): + packet = Transport.destination_table[destination_hash][6] + next_hop = Transport.destination_table[destination_hash][1] + received_from = Transport.destination_table[destination_hash][5] + + if attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from: + RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_DEBUG) + + else: + if requestor_transport_id != None and next_hop == requestor_transport_id: + // TODO: Find a bandwidth efficient way to invalidate our + // known path on this signal. The obvious way of signing + // path requests with transport instance keys is quite + // inefficient. There is probably a better way. Doing + // path invalidation here would decrease the network + // convergence time. Maybe just drop it? + RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_DEBUG) + else: + RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", RNS.LOG_DEBUG) + + now = time.time() + retries = Transport.PATHFINDER_R + local_rebroadcasts = 0 + block_rebroadcasts = True + announce_hops = packet.hops + + if is_from_local_client: + retransmit_timeout = now + else: + // TODO: Look at this timing + retransmit_timeout = now + Transport.PATH_REQUEST_GRACE // + (RNS.rand() * Transport.PATHFINDER_RW) + + // This handles an edge case where a peer sends a past + // request for a destination just after an announce for + // said destination has arrived, but before it has been + // rebroadcast locally. In such a case the actual announce + // is temporarily held, and then reinserted when the path + // request has been served to the peer. + if packet.destination_hash in Transport.announce_table: + held_entry = Transport.announce_table[packet.destination_hash] + Transport.held_announces[packet.destination_hash] = held_entry + + Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, announce_hops, packet, local_rebroadcasts, block_rebroadcasts, attached_interface] + + elif is_from_local_client: + // Forward path request on all interfaces + // except the local client + RNS.log("Forwarding path request from local client for "+RNS.prettyhexrep(destination_hash)+interface_str+" to all other interfaces", RNS.LOG_DEBUG) + request_tag = RNS.Identity.get_random_hash() + for interface in Transport.interfaces: + if not interface == attached_interface: + Transport.request_path(destination_hash, interface, tag = request_tag) + + elif should_search_for_unknown: + if destination_hash in Transport.discovery_path_requests: + RNS.log("There is already a waiting path request for "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) + else: + // Forward path request on all interfaces + // except the requestor interface + RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) + pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface } + Transport.discovery_path_requests[destination_hash] = pr_entry + + for interface in Transport.interfaces: + if not interface == attached_interface: + // Use the previously extracted tag from this path request + // on the new path requests as well, to avoid potential loops + Transport.request_path(destination_hash, on_interface=interface, tag=tag, recursive=True) + + elif not is_from_local_client and len(Transport.local_client_interfaces) > 0: + // Forward the path request on all local + // client interfaces + RNS.log("Forwarding path request for "+RNS.prettyhexrep(destination_hash)+interface_str+" to local clients", RNS.LOG_DEBUG) + for interface in Transport.local_client_interfaces: + Transport.request_path(destination_hash, on_interface=interface) + + else: + RNS.log("Ignoring path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", no path known", RNS.LOG_DEBUG) +*/ +} + +/*static*/ bool Transport::from_local_client(const Packet &packet) { +/* + if hasattr(packet.receiving_interface, "parent_interface"): + return Transport.is_local_client_interface(packet.receiving_interface) + else: + return False +*/ + // MOCK + return false; +} + +/*static*/ bool Transport::is_local_client_interface(const Interface &interface) { +/* + if hasattr(interface, "parent_interface"): + if hasattr(interface.parent_interface, "is_local_shared_instance"): + return True + else: + return False + else: + return False +*/ + // MOCK + return false; +} + +/*static*/ bool Transport::interface_to_shared_instance(const Interface &interface) { +/* + if hasattr(interface, "is_connected_to_shared_instance"): + return True + else: + return False +*/ + // MOCK + return false; +} + +/*static*/ void Transport::detach_interfaces() { +/* + detachable_interfaces = [] + + for interface in Transport.interfaces: + // Currently no rules are being applied + // here, and all interfaces will be sent + // the detach call on RNS teardown. + if True: + detachable_interfaces.append(interface) + else: + pass + + for interface in Transport.local_client_interfaces: + // Currently no rules are being applied + // here, and all interfaces will be sent + // the detach call on RNS teardown. + if True: + detachable_interfaces.append(interface) + else: + pass + + for interface in detachable_interfaces: + interface.detach() +*/ +} + +/*static*/ void Transport::shared_connection_disappeared() { +/* + for link in Transport.active_links: + link.teardown() + + for link in Transport.pending_links: + link.teardown() + + Transport.announce_table = {} + Transport.destination_table = {} + Transport.reverse_table = {} + Transport.link_table = {} + Transport.held_announces = {} + Transport.announce_handlers = [] + Transport.tunnels = {} +*/ +} + +/*static*/ void Transport::shared_connection_reappeared() { +/* + if Transport.owner.is_connected_to_shared_instance: + for registered_destination in Transport.destinations: + if registered_destination.type == RNS.Destination.SINGLE: + registered_destination.announce(path_response=True) +*/ +} + +/*static*/ void Transport::drop_announce_queues() { +/* + for interface in Transport.interfaces: + if hasattr(interface, "announce_queue") and interface.announce_queue != None: + na = len(interface.announce_queue) + if na > 0: + if na == 1: + na_str = "1 announce" + else: + na_str = str(na)+" announces" + + interface.announce_queue = [] + RNS.log("Dropped "+na_str+" on "+str(interface), RNS.LOG_VERBOSE) +*/ +} + +/*static*/ bool Transport::announce_emitted(const Packet &packet) { +/* + random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10] + announce_emitted = int.from_bytes(random_blob[5:10], "big") + + return announce_emitted +*/ + // MOCK + return false; +} + +/*static*/ void Transport::save_packet_hashlist() { +/* + if not Transport.owner.is_connected_to_shared_instance: + if hasattr(Transport, "saving_packet_hashlist"): + wait_interval = 0.2 + wait_timeout = 5 + wait_start = time.time() + while Transport.saving_packet_hashlist: + time.sleep(wait_interval) + if time.time() > wait_start+wait_timeout: + RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR) + return False + + try: + Transport.saving_packet_hashlist = True + save_start = time.time() + + if not RNS.Reticulum.transport_enabled(): + Transport.packet_hashlist = [] + else: + RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG) + + packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist" + file = open(packet_hashlist_path, "wb") + file.write(umsgpack.packb(Transport.packet_hashlist)) + file.close() + + save_time = time.time() - save_start + if save_time < 1: + time_str = str(round(save_time*1000,2))+"ms" + else: + time_str = str(round(save_time,2))+"s" + RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG) + + except Exception as e: + RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + Transport.saving_packet_hashlist = False +*/ +} + +/*static*/ void Transport::save_path_table() { +/* + if not Transport.owner.is_connected_to_shared_instance: + if hasattr(Transport, "saving_path_table"): + wait_interval = 0.2 + wait_timeout = 5 + wait_start = time.time() + while Transport.saving_path_table: + time.sleep(wait_interval) + if time.time() > wait_start+wait_timeout: + RNS.log("Could not save path table to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR) + return False + + try: + Transport.saving_path_table = True + save_start = time.time() + RNS.log("Saving path table to storage...", RNS.LOG_DEBUG) + + serialised_destinations = [] + for destination_hash in Transport.destination_table: + // Get the destination entry from the destination table + de = Transport.destination_table[destination_hash] + interface_hash = de[5].get_hash() + + // Only store destination table entry if the associated + // interface is still active + interface = Transport.find_interface_from_hash(interface_hash) + if interface != None: + // Get the destination entry from the destination table + de = Transport.destination_table[destination_hash] + timestamp = de[0] + received_from = de[1] + hops = de[2] + expires = de[3] + random_blobs = de[4] + packet_hash = de[6].get_hash() + + serialised_entry = [ + destination_hash, + timestamp, + received_from, + hops, + expires, + random_blobs, + interface_hash, + packet_hash + ] + + serialised_destinations.append(serialised_entry) + + Transport.cache(de[6], force_cache=True) + + destination_table_path = RNS.Reticulum.storagepath+"/destination_table" + file = open(destination_table_path, "wb") + file.write(umsgpack.packb(serialised_destinations)) + file.close() + + save_time = time.time() - save_start + if save_time < 1: + time_str = str(round(save_time*1000,2))+"ms" + else: + time_str = str(round(save_time,2))+"s" + RNS.log("Saved "+str(len(serialised_destinations))+" path table entries in "+time_str, RNS.LOG_DEBUG) + + except Exception as e: + RNS.log("Could not save path table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + Transport.saving_path_table = False +*/ +} + +/*static*/ void Transport::save_tunnel_table() { +/* + if not Transport.owner.is_connected_to_shared_instance: + if hasattr(Transport, "saving_tunnel_table"): + wait_interval = 0.2 + wait_timeout = 5 + wait_start = time.time() + while Transport.saving_tunnel_table: + time.sleep(wait_interval) + if time.time() > wait_start+wait_timeout: + RNS.log("Could not save tunnel table to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR) + return False + + try: + Transport.saving_tunnel_table = True + save_start = time.time() + RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG) + + serialised_tunnels = [] + for tunnel_id in Transport.tunnels: + te = Transport.tunnels[tunnel_id] + interface = te[1] + tunnel_paths = te[2] + expires = te[3] + + if interface != None: + interface_hash = interface.get_hash() + else: + interface_hash = None + + serialised_paths = [] + for destination_hash in tunnel_paths: + de = tunnel_paths[destination_hash] + + timestamp = de[0] + received_from = de[1] + hops = de[2] + expires = de[3] + random_blobs = de[4] + packet_hash = de[6].get_hash() + + serialised_entry = [ + destination_hash, + timestamp, + received_from, + hops, + expires, + random_blobs, + interface_hash, + packet_hash + ] + + serialised_paths.append(serialised_entry) + + Transport.cache(de[6], force_cache=True) + + + serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires] + serialised_tunnels.append(serialised_tunnel) + + tunnels_path = RNS.Reticulum.storagepath+"/tunnels" + file = open(tunnels_path, "wb") + file.write(umsgpack.packb(serialised_tunnels)) + file.close() + + save_time = time.time() - save_start + if save_time < 1: + time_str = str(round(save_time*1000,2))+"ms" + else: + time_str = str(round(save_time,2))+"s" + RNS.log("Saved "+str(len(serialised_tunnels))+" tunnel table entries in "+time_str, RNS.LOG_DEBUG) + except Exception as e: + RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR) + + Transport.saving_tunnel_table = False +*/ +} + +/*static*/ void Transport::persist_data() { + save_packet_hashlist(); + save_path_table(); + save_tunnel_table(); +} + +/*static*/ void Transport::exit_handler() { + extreme("Transport::exit_handler()"); + if (!_owner.is_connected_to_shared_instance()) { + persist_data(); + } } diff --git a/src/Transport.h b/src/Transport.h index f0931c0..8d697d3 100644 --- a/src/Transport.h +++ b/src/Transport.h @@ -1,29 +1,150 @@ #pragma once +#include "Reticulum.h" #include "Link.h" +// CBA TODO resolve circular dependency with following header file +//#include "Packet.h" +#include "Bytes.h" +#include "None.h" +#include "Interfaces/Interface.h" +#include +#include +#include +#include +#include +#include +#include namespace RNS { class Packet; + class PacketReceipt; class Destination; + class Interface; + class Link; + class Identity; + class AnnounceHandler { + public: + AnnounceHandler(const std::string &aspect_filter) { + _aspect_filter = aspect_filter; + } + virtual void received_announce(const Bytes &destination_hash, const Identity &announced_identity, const Bytes &app_data) = 0; + private: + std::string _aspect_filter; + }; + using HAnnounceHandler = std::shared_ptr; + + /* + Through static methods of this class you can interact with the + Transport system of Reticulum. + */ class Transport { + private: + class DestinationEntry { + public: + DestinationEntry(uint64_t time, Bytes received_from, uint8_t announce_hops, uint64_t expires, std::set random_blobs, Interface &receiving_interface, const Packet &packet) : + _timestamp(time), + _received_from(received_from), + _hops(announce_hops), + _expires(expires), + _random_blobs(random_blobs), + _receiving_interface(receiving_interface), + _packet(packet) + { + } + public: + uint64_t _timestamp = 0; + Bytes _received_from; + uint8_t _hops = 0; + uint64_t _expires = 0; + std::set _random_blobs; + Interface &_receiving_interface; + const Packet &_packet; + }; + + class AnnounceEntry { + public: + AnnounceEntry(uint64_t timestamp, uint16_t retransmit_timeout, uint8_t retries, Bytes received_from, uint8_t hops, const Packet &packet, uint8_t local_rebroadcasts, bool block_rebroadcasts, const Interface &attached_interface) : + _timestamp(timestamp), + _retransmit_timeout(retransmit_timeout), + _retries(retries), + _received_from(received_from), + _hops(hops), + _packet(packet), + _local_rebroadcasts(local_rebroadcasts), + _block_rebroadcasts(block_rebroadcasts), + _attached_interface(attached_interface) + { + } + public: + uint64_t _timestamp = 0; + uint16_t _retransmit_timeout = 0; + uint8_t _retries = 0; + Bytes _received_from; + uint8_t _hops = 0; + const Packet &_packet; + uint8_t _local_rebroadcasts = 0; + bool _block_rebroadcasts = false; + const Interface &_attached_interface; + }; + + class LinkEntry { + public: + LinkEntry(uint64_t timestamp, const Bytes &next_hop, const Interface &outbound_interface, uint8_t remaining_hops, const Interface &receiving_interface, uint8_t hops, const Bytes &destination_hash, bool validated, uint64_t proof_timeout) : + _timestamp(timestamp), + _next_hop(next_hop), + _outbound_interface(outbound_interface), + _remaining_hops(remaining_hops), + _receiving_interface(receiving_interface), + _hops(hops), + _destination_hash(destination_hash), + _validated(validated), + _proof_timeout(proof_timeout) + { + } + public: + uint64_t _timestamp = 0; + Bytes _next_hop; + const Interface &_outbound_interface; + uint8_t _remaining_hops = 0; + const Interface &_receiving_interface; + uint8_t _hops = 0; + Bytes _destination_hash; + bool _validated = false; + uint64_t _proof_timeout = 0; + }; + + class ReverseEntry { + public: + ReverseEntry(const Interface &receiving_interface, const Interface &outbound_interface, uint64_t timestamp) : + _receiving_interface(receiving_interface), + _outbound_interface(outbound_interface), + _timestamp(timestamp) + { + } + public: + const Interface &_receiving_interface; + const Interface &_outbound_interface; + uint64_t _timestamp = 0; + }; + public: // Constants enum types { - BROADCAST = 0x00, - TRANSPORT = 0x01, - RELAY = 0x02, - TUNNEL = 0x03, - NONE = 0xFF, + BROADCAST = 0x00, + TRANSPORT = 0x01, + RELAY = 0x02, + TUNNEL = 0x03, + NONE = 0xFF, }; enum reachabilities { - REACHABILITY_UNREACHABLE = 0x00, - REACHABILITY_DIRECT = 0x01, - REACHABILITY_TRANSPORT = 0x02, + REACHABILITY_UNREACHABLE = 0x00, + REACHABILITY_DIRECT = 0x01, + REACHABILITY_TRANSPORT = 0x02, }; static constexpr const char* APP_NAME = "rnstransport"; @@ -54,13 +175,112 @@ namespace RNS { static const uint8_t MAX_RATE_TIMESTAMPS = 16; // Maximum number of announce timestamps to keep per destination public: - Transport(); - ~Transport(); - - public: + static void start(const Reticulum &reticulum_instance); + static void jobloop(); + static void jobs(); + static void transmit(Interface &interface, const Bytes &raw); + static bool outbound(Packet &packet); + static bool packet_filter(const Packet &packet); + static void inbound(const Bytes &raw, const Interface &interface = Interface::NONE); + static void synthesize_tunnel(const Interface &interface); + static void tunnel_synthesize_handler(const Bytes &data, const Packet &packet); + static void handle_tunnel(const Bytes &tunnel_id, const Interface &interface); + static void register_interface(Interface &interface); + static void deregister_interface(const Interface &interface); static void register_destination(Destination &destination); - static bool outbound(const Packet &packet); + static void deregister_destination(const Destination &destination); + static void register_link(const Link &link); + static void activate_link(Link &link); + static void register_announce_handler(HAnnounceHandler handler); + static void deregister_announce_handler(HAnnounceHandler handler); + static Interface find_interface_from_hash(const Bytes &interface_hash); + static bool should_cache(const Packet &packet); + static void cache(const Packet &packet, bool force_cache = false); + static Packet get_cached_packet(const Bytes &packet_hash); + static bool cache_request_packet(const Packet &packet); + static void cache_request(const Bytes &packet_hash, const Destination &destination); + static bool has_path(const Bytes &destination_hash); + static uint8_t hops_to(const Bytes &destination_hash); + static Bytes next_hop(const Bytes &destination_hash); + static Interface next_hop_interface(const Bytes &destination_hash); + static bool expire_path(const Bytes &destination_hash); + static void request_path(const Bytes &destination_hash, const Interface &on_interface = {Interface::NONE}, const Bytes &tag = {}, bool recursive = false); + static void path_request_handler(const Bytes &data, const Packet &packet); + static void path_request(const Bytes &destination_hash, bool is_from_local_client, const Interface &attached_interface, const Bytes &requestor_transport_id = {}, const Bytes &tag = {}); + static bool from_local_client(const Packet &packet); + static bool is_local_client_interface(const Interface &interface); + static bool interface_to_shared_instance(const Interface &interface); + static void detach_interfaces(); + static void shared_connection_disappeared(); + static void shared_connection_reappeared(); + static void drop_announce_queues(); + static bool announce_emitted(const Packet &packet); + static void save_packet_hashlist(); + static void save_path_table(); + static void save_tunnel_table(); + static void persist_data(); + static void exit_handler(); + private: + //static std::set, std::less> _interfaces; // All active interfaces + static std::list> _interfaces; // All active interfaces + static std::set _destinations; // All active destinations + //static std::map _destinations; // All active destinations + static std::set _pending_links; // Links that are being established + static std::set _active_links; // Links that are active + static std::set _packet_hashlist; // A list of packet hashes for duplicate detection + static std::set _receipts; // Receipts of all outgoing packets for proof processing + + // TODO: "destination_table" should really be renamed to "path_table" + // Notes on memory usage: 1 megabyte of memory can store approximately + // 55.100 path table entries or approximately 22.300 link table entries. + + static std::map _announce_table; // A table for storing announces currently waiting to be retransmitted + static std::map _destination_table; // A lookup table containing the next hop to a given destination + static std::map _reverse_table; // A lookup table for storing packet hashes used to return proofs and replies + static std::map _link_table; // A lookup table containing hops for links + static std::map _held_announces; // A table containing temporarily held announce-table entries + static std::set _announce_handlers; // A table storing externally registered announce handlers + //z_tunnels = {} // A table storing tunnels to other transport instances + //z_announce_rate_table = {} // A table for keeping track of announce rates + static std::set _path_requests; // A table for storing path request timestamps + + //z_discovery_path_requests = {} // A table for keeping track of path requests on behalf of other nodes + //z_discovery_pr_tags = [] // A table for keeping track of tagged path requests + static uint16_t _max_pr_taXgxs; // Maximum amount of unique path request tags to remember + + // Transport control destinations are used + // for control purposes like path requests + static std::set _control_destinations; + static std::set _control_hashes; + + // Interfaces for communicating with + // local clients connected to a shared + // Reticulum instance + static std::set _local_client_interfaces; + + //z_local_client_rssi_cache = [] + //z_local_client_snr_cache = [] + static uint16_t _LOCAL_CLIENT_CACHE_MAXSIZE; + + std::map _pending_local_path_requests; + + static uint64_t _start_time; + static bool _jobs_locked; + static bool _jobs_running; + static uint32_t _job_interval; + static uint64_t _links_last_checked; + static uint32_t _links_check_interval; + static uint64_t _receipts_last_checked; + static uint32_t _receipts_check_interval; + static uint64_t _announces_last_checked; + static uint32_t _announces_check_interval; + static uint32_t _hashlist_maxsize; + static uint64_t _tables_last_culled; + static uint32_t _tables_cull_interval; + + static Reticulum _owner; + static Identity _identity; }; } \ No newline at end of file diff --git a/src/Utilities/OS.h b/src/Utilities/OS.h new file mode 100644 index 0000000..e0f5739 --- /dev/null +++ b/src/Utilities/OS.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace RNS { namespace Utilities { + + class OS { + + public: + // sleep for specified milliseconds + static inline void sleep(float seconds) { ::sleep(seconds); } + //static inline void sleep(uint32_t milliseconds) { ::sleep((float)milliseconds / 1000.0); } + // return current time in milliseconds since 00:00:00, January 1, 1970 (Unix Epoch) + static uint64_t time() { timeval time; ::gettimeofday(&time, NULL); return (uint64_t)(time.tv_sec * 1000) + (uint64_t)(time.tv_usec / 1000); } + static inline float round(float value, uint8_t precision) { return std::round(value / precision) * precision; } + + }; + +} } diff --git a/src/main.cpp b/src/main.cpp index 85c13e4..86d2c78 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "Identity.h" #include "Destination.h" #include "Packet.h" +#include "Interfaces/Interface.h" #include "Bytes.h" #ifndef NATIVE @@ -17,6 +18,7 @@ #include #include #include +#include //#include // Let's define an app name. We'll use this for all @@ -29,9 +31,98 @@ const char* APP_NAME = "example_utilities"; const char* fruits[] = {"Peach", "Quince", "Date", "Tangerine", "Pomelo", "Carambola", "Grape"}; const char* noble_gases[] = {"Helium", "Neon", "Argon", "Krypton", "Xenon", "Radon", "Oganesson"}; +class TestInterface : public RNS::Interface { +public: + TestInterface() : RNS::Interface("TestInterface") { + IN(true); + OUT(true); + } + TestInterface(const char *name) : RNS::Interface(name) { + IN(true); + OUT(true); + } + virtual ~TestInterface() { + name("deleted"); + } + virtual void processIncoming(const RNS::Bytes &data) { + RNS::extreme("TestInterface.processIncoming: data: " + data.toHex()); + } + virtual void processOutgoing(const RNS::Bytes &data) { + RNS::extreme("TestInterface.processOutgoing: data: " + data.toHex()); + } + virtual inline std::string toString() const { return "TestInterface[" + name() + "]"; } +}; + +class TestLoopbackInterface : public RNS::Interface { +public: + TestLoopbackInterface(RNS::Interface &loopback_interface) : RNS::Interface("TestLoopbackInterface"), _loopback_interface(loopback_interface) { + IN(true); + OUT(true); + } + TestLoopbackInterface(RNS::Interface &loopback_interface, const char *name) : RNS::Interface(name), _loopback_interface(loopback_interface) { + IN(true); + OUT(true); + } + virtual ~TestLoopbackInterface() { + name("deleted"); + } + virtual void processIncoming(const RNS::Bytes &data) { + RNS::extreme("TestLoopbackInterface.processIncoming: data: " + data.toHex()); + _loopback_interface.processOutgoing(data); + } + virtual void processOutgoing(const RNS::Bytes &data) { + RNS::extreme("TestLoopbackInterface.processOutgoing: data: " + data.toHex()); + _loopback_interface.processIncoming(data); + } + virtual inline std::string toString() const { return "TestLoopbackInterface[" + name() + "]"; } +private: + RNS::Interface &_loopback_interface; +}; + +class TestOutInterface : public RNS::Interface { +public: + TestOutInterface() : RNS::Interface("TestOutInterface") { + OUT(true); + IN(false); + } + TestOutInterface(const char *name) : RNS::Interface(name) { + OUT(true); + IN(false); + } + virtual ~TestOutInterface() { + name("(deleted)"); + } + virtual void processOutgoing(const RNS::Bytes &data) { + RNS::head("TestOutInterface.processOutgoing: data: " + data.toHex(), RNS::LOG_EXTREME); + RNS::Interface::processOutgoing(data); + } + virtual inline std::string toString() const { return "TestOutInterface[" + name() + "]"; } +}; + +class TestInInterface : public RNS::Interface { +public: + TestInInterface() : RNS::Interface("TestInInterface") { + OUT(false); + IN(true); + } + TestInInterface(const char *name) : RNS::Interface(name) { + OUT(false); + IN(true); + } + virtual ~TestInInterface() { + name("(deleted)"); + } + virtual void processIncoming(const RNS::Bytes &data) { + RNS::head("TestInInterface.processIncoming: data: " + data.toHex(), RNS::LOG_EXTREME); + RNS::Interface::processIncoming(data); + } + virtual inline std::string toString() const { return "TestInInterface[" + name() + "]"; } +}; + void onPacket(const RNS::Bytes &data, const RNS::Packet &packet) { - RNS::extreme("onPacket: data: " + data.toHex()); - RNS::extreme("onPacket: data string: \"" + data.toString() + "\""); + RNS::head("onPacket: data: " + data.toHex(), RNS::LOG_EXTREME); + RNS::head("onPacket: data string: \"" + data.toString() + "\"", RNS::LOG_EXTREME); + //RNS::head("onPacket: " + packet.debugString(), RNS::LOG_EXTREME); } void setup() { @@ -46,7 +137,11 @@ void setup() { #ifndef NDEBUG RNS::loglevel(RNS::LOG_WARNING); //RNS::loglevel(RNS::LOG_EXTREME); + RNS::extreme("Running tests..."); test(); + //testReference(); + //testCrypto(); + RNS::extreme("Finished running tests"); #endif //std::stringstream test; @@ -59,22 +154,58 @@ void setup() { // 21.8% baseline here with serial + RNS::head("Creating Reticulum instance...", RNS::LOG_EXTREME); RNS::Reticulum reticulum; // 21.9% (+0.1%) + RNS::head("Creating Interface instances...", RNS::LOG_EXTREME); + //TestInterface interface; + TestOutInterface outinterface; + TestInInterface ininterface; + TestLoopbackInterface loopinterface(ininterface); + + RNS::head("Registering Interface instances with Transport...", RNS::LOG_EXTREME); + //RNS::Transport::register_interface(interface); + RNS::Transport::register_interface(outinterface); + RNS::Transport::register_interface(ininterface); + RNS::Transport::register_interface(loopinterface); + + RNS::head("Creating Identity instance...", RNS::LOG_EXTREME); RNS::Identity identity; // 22.6% (+0.7%) + RNS::head("Creating Destination instance...", RNS::LOG_EXTREME); RNS::Destination destination(identity, RNS::Destination::IN, RNS::Destination::SINGLE, "test", "context"); // 23.0% (+0.4%) +/* + RNS::head("Testing map...", RNS::LOG_EXTREME); + { + std::map destinations; + destinations.insert({destination.hash(), destination}); + //for (RNS::Destination &destination : destinations) { + for (auto &[hash, destination] : destinations) { + RNS::extreme("Iterated destination: " + destination.toString()); + } + RNS::Bytes hash = destination.hash(); + auto iter = destinations.find(hash); + if (iter != destinations.end()) { + RNS::Destination &destination = (*iter).second; + RNS::extreme("Found destination: " + destination.toString()); + } + return; + } +*/ + destination.set_proof_strategy(RNS::Destination::PROVE_ALL); + //zRNS::head("Registering announce handler with Transport...", RNS::LOG_EXTREME); //zannounce_handler = ExampleAnnounceHandler( //z aspect_filter="example_utilities.announcesample.fruits"; //z) //zRNS::Transport.register_announce_handler(announce_handler); + RNS::head("Announcing destination...", RNS::LOG_EXTREME); //destination.announce(RNS::bytesFromString(fruits[rand() % 7])); // test path //destination.announce(RNS::bytesFromString(fruits[rand() % 7]), true, nullptr, RNS::bytesFromString("test_tag")); @@ -82,17 +213,27 @@ void setup() { destination.announce(RNS::bytesFromString(fruits[rand() % 7])); // 23.9% (+0.8%) +/* // test data send packet + RNS::head("Creating send packet...", RNS::LOG_EXTREME); RNS::Packet send_packet(destination, "The quick brown fox jumps over the lazy dog"); + + RNS::head("Sending send packet...", RNS::LOG_EXTREME); send_packet.pack(); - RNS::extreme("Test send_packet packet: " + send_packet.debugString()); + RNS::extreme("Test send_packet: " + send_packet.debugString()); // test data receive packet + RNS::head("Registering packet callback with Destination...", RNS::LOG_EXTREME); destination.set_packet_callback(onPacket); + + RNS::head("Creating recv packet...", RNS::LOG_EXTREME); RNS::Packet recv_packet(RNS::Destination::NONE, send_packet.raw()); recv_packet.unpack(); - RNS::extreme("Test recv_packet packet: " + recv_packet.debugString()); + RNS::extreme("Test recv_packet: " + recv_packet.debugString()); + + RNS::head("Spoofing recv packet to destination...", RNS::LOG_EXTREME); destination.receive(recv_packet); +*/ } catch (std::exception& e) { @@ -110,6 +251,83 @@ void loop() { int main(void) { printf("Hello from Native on PlatformIO!\n"); +/* + RNS::loglevel(RNS::LOG_EXTREME); + TestInterface testinterface; + + std::set, std::less> interfaces; + interfaces.insert(testinterface); + for (auto iter = interfaces.begin(); iter != interfaces.end(); ++iter) { + RNS::Interface &interface = (*iter); + RNS::extreme("Found interface: " + interface.toString()); + RNS::Bytes data; + const_cast(interface).processOutgoing(data); + } + return 0; +*/ +/* + RNS::loglevel(RNS::LOG_EXTREME); + TestInterface testinterface; + + std::set, std::less> interfaces; + interfaces.insert(testinterface); + for (auto &interface : interfaces) { + RNS::extreme("Found interface: " + interface.toString()); + RNS::Bytes data; + const_cast(interface).processOutgoing(data); + } + return 0; +*/ +/* + RNS::loglevel(RNS::LOG_EXTREME); + TestInterface testinterface; + + std::list> interfaces; + interfaces.push_back(testinterface); + for (auto iter = interfaces.begin(); iter != interfaces.end(); ++iter) { + RNS::Interface &interface = (*iter); + RNS::extreme("Found interface: " + interface.toString()); + RNS::Bytes data; + const_cast(interface).processOutgoing(data); + } + return 0; +*/ +/* + RNS::loglevel(RNS::LOG_EXTREME); + TestInterface testinterface; + + std::list> interfaces; + interfaces.push_back(testinterface); + //for (auto &interface : interfaces) { + for (RNS::Interface &interface : interfaces) { + RNS::extreme("Found interface: " + interface.toString()); + RNS::Bytes data; + const_cast(interface).processOutgoing(data); + } + return 0; +*/ +/* + std::list> interfaces; + { + RNS::loglevel(RNS::LOG_EXTREME); + TestInterface testinterface; + interfaces.push_back(testinterface); + for (auto iter = interfaces.begin(); iter != interfaces.end(); ++iter) { + RNS::Interface &interface = (*iter); + RNS::extreme("1 Found interface: " + interface.toString()); + RNS::Bytes data; + const_cast(interface).processOutgoing(data); + } + } + for (auto iter = interfaces.begin(); iter != interfaces.end(); ++iter) { + RNS::Interface &interface = (*iter); + RNS::extreme("2 Found interface: " + interface.toString()); + RNS::Bytes data; + const_cast(interface).processOutgoing(data); + } + return 0; +*/ + setup(); //while (true) {