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

Firmware changes:
- Set is_backbone(true) on backbone TCP interface
- Rename default TcpInterface name to BackboneInterface
- Update comments for dual-use TcpInterface (backbone + local AP)
- Use vendored lib/microReticulum instead of PlatformIO registry
2026-02-23 18:08:29 -05:00

4306 lines
161 KiB
C++
Executable File

#include "Transport.h"
#include "Link.h"
#include "Reticulum.h"
#include "Destination.h"
#include "Identity.h"
#include "Packet.h"
#include "Interface.h"
#include "Log.h"
#include "Cryptography/Random.h"
#include "Utilities/OS.h"
#include "Utilities/Persistence.h"
#include <algorithm>
#include <unistd.h>
#include <time.h>
using namespace RNS;
using namespace RNS::Type::Transport;
using namespace RNS::Utilities;
#if defined(INTERFACES_SET)
///*static*/ std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> Transport::_interfaces;
/*static*/ std::set<std::reference_wrapper<Interface>, std::less<Interface>> Transport::_interfaces;
#elif defined(INTERFACES_LIST)
/*static*/ std::list<std::reference_wrapper<Interface>> Transport::_interfaces;
#elif defined(INTERFACES_MAP)
/*static*/ std::map<Bytes, Interface&> Transport::_interfaces;
#endif
#if defined(DESTINATIONS_SET)
/*static*/ std::set<Destination> Transport::_destinations;
#elif defined(DESTINATIONS_MAP)
/*static*/ std::map<Bytes, Destination> Transport::_destinations;
#endif
/*static*/ std::set<Link> Transport::_pending_links;
/*static*/ std::set<Link> Transport::_active_links;
/*static*/ std::set<Bytes> Transport::_packet_hashlist;
/*static*/ std::list<PacketReceipt> Transport::_receipts;
/*static*/ std::map<Bytes, Transport::AnnounceEntry> Transport::_announce_table;
/*static*/ std::map<Bytes, Transport::DestinationEntry> Transport::_destination_table;
/*static*/ std::map<Bytes, Transport::ReverseEntry> Transport::_reverse_table;
/*static*/ std::map<Bytes, Transport::LinkEntry> Transport::_link_table;
/*static*/ std::map<Bytes, Transport::AnnounceEntry> Transport::_held_announces;
/*static*/ std::set<HAnnounceHandler> Transport::_announce_handlers;
/*static*/ std::map<Bytes, Transport::TunnelEntry> Transport::_tunnels;
/*static*/ std::map<Bytes, Transport::RateEntry> Transport::_announce_rate_table;
/*static*/ std::map<Bytes, double> Transport::_path_requests;
/*static*/ std::map<Bytes, Transport::PathRequestEntry> Transport::_discovery_path_requests;
/*static*/ std::set<Bytes> Transport::_discovery_pr_tags;
/*static*/ std::set<Destination> Transport::_control_destinations;
/*static*/ std::set<Bytes> Transport::_control_hashes;
///*static*/ std::set<Interface> Transport::_local_client_interfaces;
/*static*/ std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> Transport::_local_client_interfaces;
/*static*/ std::map<Bytes, const Interface&> Transport::_pending_local_path_requests;
// CBA
/*static*/ std::map<Bytes, Transport::PacketEntry> Transport::_packet_table;
/*static*/ uint16_t Transport::_LOCAL_CLIENT_CACHE_MAXSIZE = 512;
/*static*/ double Transport::_start_time = 0.0;
/*static*/ bool Transport::_jobs_locked = false;
/*static*/ bool Transport::_jobs_running = false;
/*static*/ float Transport::_job_interval = 0.250;
/*static*/ double Transport::_jobs_last_run = 0.0;
/*static*/ double Transport::_links_last_checked = 0.0;
/*static*/ float Transport::_links_check_interval = 1.0;
/*static*/ double Transport::_receipts_last_checked = 0.0;
/*static*/ float Transport::_receipts_check_interval = 1.0;
/*static*/ double Transport::_announces_last_checked = 0.0;
/*static*/ float Transport::_announces_check_interval = 1.0;
/*static*/ double Transport::_tables_last_culled = 0.0;
// CBA MCU
/*static*/ //float Transport::_tables_cull_interval = 5.0;
/*static*/ float Transport::_tables_cull_interval = 60.0;
/*static*/ bool Transport::_saving_path_table = false;
// CBA ACCUMULATES
// CBA MCU
/*static*/ //uint16_t Transport::_hashlist_maxsize = 1000000;
/*static*/ //uint16_t Transport::_hashlist_maxsize = 100;
/*static*/ uint16_t Transport::_hashlist_maxsize = 100;
// CBA ACCUMULATES
// CBA MCU
/*static*/ //uint16_t Transport::_max_pr_tags = 32000;
/*static*/ uint16_t Transport::_max_pr_tags = 32;
// CBA
// CBA ACCUMULATES
/*static*/ uint16_t Transport::_path_table_maxsize = 100;
// CBA ACCUMULATES
/*static*/ uint16_t Transport::_path_table_maxpersist = 100;
/*static*/ double Transport::_last_saved = 0.0;
/*static*/ float Transport::_save_interval = 3600.0;
/*static*/ uint32_t Transport::_destination_table_crc = 0;
/*static*/ Reticulum Transport::_owner({Type::NONE});
// BOUNDARY MODE Whitelist 1: addresses of local devices (from LoRa and LocalTCP interfaces)
static std::set<Bytes> _boundary_local_addresses;
// BOUNDARY MODE Whitelist 2: addresses mentioned in packets from local devices
static std::set<Bytes> _boundary_mentioned_addresses;
// BOUNDARY MODE: Check if an interface is the backbone
static bool is_backbone_interface(const Interface& iface) {
return iface.is_backbone();
}
/*static*/ Identity Transport::_identity({Type::NONE});
// CBA
/*static*/ Transport::Callbacks Transport::_callbacks;
// CBA Stats
/*static*/ uint32_t Transport::_packets_sent = 0;
/*static*/ uint32_t Transport::_packets_received = 0;
/*static*/ uint32_t Transport::_destinations_added = 0;
/*static*/ size_t Transport::_last_memory = 0;
/*static*/ size_t Transport::_last_flash = 0;
/*static*/ void Transport::start(const Reticulum& reticulum_instance) {
INFO("Transport starting...");
_jobs_running = true;
_owner = reticulum_instance;
// Initialize time-based variables *after* time offset update
_jobs_last_run = OS::time();
_links_last_checked = OS::time();
_receipts_last_checked = OS::time();
_announces_last_checked = OS::time();
_tables_last_culled = OS::time();
_last_saved = OS::time();
// ensure required directories exist
if (!OS::directory_exists(Reticulum::_cachepath)) {
VERBOSE("No cache directory, creating...");
OS::create_directory(Reticulum::_cachepath);
}
if (!_identity) {
char transport_identity_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(transport_identity_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/transport_identity", Reticulum::_storagepath);
DEBUG("Checking for transport identity...");
try {
if (OS::file_exists(transport_identity_path)) {
_identity = Identity::from_file(transport_identity_path);
}
if (!_identity) {
VERBOSE("No valid Transport Identity in storage, creating...");
_identity = Identity();
_identity.to_file(transport_identity_path);
}
else {
VERBOSE("Loaded Transport Identity from storage");
}
}
catch (std::exception& e) {
ERRORF("Failed to check for transport identity, the contained exception was: %s", e.what());
}
}
// TODO
/*
packet_hashlist_path = Reticulum::storagepath + "/packet_hashlist";
if (!owner.is_connected_to_shared_instance()) {
if (os.path.isfile(packet_hashlist_path)) {
try {
//p file = open(packet_hashlist_path, "rb")
//p Transport.packet_hashlist = umsgpack.unpackb(file.read())
//p file.close()
}
catch (std::exception& e) {
ERRORF("Could not load packet hashlist from storage, the contained exception was: %s", e.what());
}
}
}
*/
// Create transport-specific destination for path request
Destination path_request_destination({Type::NONE}, Type::Destination::IN, Type::Destination::PLAIN, APP_NAME, "path.request");
path_request_destination.set_packet_callback(path_request_handler);
// CBA ACCUMULATES
_control_destinations.insert(path_request_destination);
// CBA ACCUMULATES
_control_hashes.insert(path_request_destination.hash());
DEBUG("Created transport-specific path request destination " + path_request_destination.hash().toHex());
// Create transport-specific destination for tunnel synthesize
Destination tunnel_synthesize_destination({Type::NONE}, Type::Destination::IN, Type::Destination::PLAIN, APP_NAME, "tunnel.synthesize");
tunnel_synthesize_destination.set_packet_callback(tunnel_synthesize_handler);
// CBA BUG?
//p Transport.control_destinations.append(Transport.tunnel_synthesize_handler)
// CBA ACCUMULATES
_control_destinations.insert(tunnel_synthesize_destination);
// CBA ACCUMULATES
_control_hashes.insert(tunnel_synthesize_destination.hash());
DEBUG("Created transport-specific tunnel synthesize destination " + tunnel_synthesize_destination.hash().toHex());
_jobs_running = false;
// CBA Threading
//p thread = threading.Thread(target=Transport.jobloop, daemon=True)
//p thread.start()
if (Reticulum::transport_enabled()) {
INFO("Transport mode is enabled");
// Read in path table and then write and clean in case any entries are invalid
read_path_table();
DEBUG("Writing path table and cleaning caches to clean-up any orphaned paths/files");
write_path_table();
clean_caches();
read_tunnel_table();
// Create transport-specific destination for probe requests
if (Reticulum::probe_destination_enabled()) {
Destination probe_destination(_identity, Type::Destination::IN, Type::Destination::SINGLE, APP_NAME, "probe");
probe_destination.accepts_links(false);
probe_destination.set_proof_strategy(Type::Destination::PROVE_ALL);
DEBUG("Created probe responder destination " + probe_destination.hash().toHex());
probe_destination.announce();
NOTICE("Transport Instance will respond to probe requests on " + probe_destination.toString());
}
VERBOSE("Transport instance " + _identity.toString() + " started");
_start_time = OS::time();
}
// TODO
/*p
// Synthesize tunnels for any interfaces wanting it
for interface in Transport.interfaces:
interface.tunnel_id = None
if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
Transport.synthesize_tunnel(interface)
*/
//#ifndef NDEBUG
// CBA DEBUG
dump_stats();
//#endif
}
/*static*/ void Transport::loop() {
if (OS::time() > (_jobs_last_run + _job_interval)) {
jobs();
_jobs_last_run = OS::time();
}
}
/*static*/ void Transport::jobs() {
//TRACE("Transport::jobs()");
std::vector<Packet> outgoing;
std::set<Bytes> path_requests;
int count;
_jobs_running = true;
try {
if (!_jobs_locked) {
// Process active and pending link lists
if (OS::time() > (_links_last_checked + _links_check_interval)) {
std::set<Link> pending_links(_pending_links);
for (auto& link : pending_links) {
if (link.status() == Type::Link::CLOSED) {
// If we are not a Transport Instance, finding a pending link
// that was never activated will trigger an expiry of the path
// to the destination, and an attempt to rediscover the path.
if (!Reticulum::transport_enabled()) {
expire_path(link.destination().hash());
// If we are connected to a shared instance, it will take
// care of sending out a new path request. If not, we will
// send one directly.
if (!_owner.is_connected_to_shared_instance()) {
double last_path_request = 0;
auto iter = _path_requests.find(link.destination().hash());
if (iter != _path_requests.end()) {
last_path_request = (*iter).second;
}
if ((OS::time() - last_path_request) > Type::Transport::PATH_REQUEST_MI) {
DEBUG("Trying to rediscover path for " + link.destination().hash().toHex() + " since an attempted link was never established");
//if (path_requests.find(link.destination().hash()) == path_requests.end()) {
if (path_requests.count(link.destination().hash()) == 0) {
// CBA ACCUMULATES
path_requests.insert(link.destination().hash());
}
}
}
}
_pending_links.erase(link);
}
}
std::set<Link> active_links(_active_links);
for (auto& link : active_links) {
if (link.status() == Type::Link::CLOSED) {
_active_links.erase(link);
}
}
_links_last_checked = OS::time();
}
// Process receipts list for timed-out packets
if (OS::time() > (_receipts_last_checked + _receipts_check_interval)) {
while (_receipts.size() > Type::Transport::MAX_RECEIPTS) {
//p culled_receipt = Transport.receipts.pop(0)
PacketReceipt culled_receipt = _receipts.front();
_receipts.pop_front();
culled_receipt.set_timeout(-1);
culled_receipt.check_timeout();
}
std::list<PacketReceipt> cull_receipts;
for (auto& receipt : _receipts) {
receipt.check_timeout();
if (receipt.status() != Type::PacketReceipt::SENT) {
//p if receipt in Transport.receipts:
//p Transport.receipts.remove(receipt)
cull_receipts.push_back(receipt);
}
}
// CBA since modifying of collection while iterating is forbidden
for (auto& receipt : _receipts) {
cull_receipts.remove(receipt);
}
_receipts_last_checked = OS::time();
}
// Process announces needing retransmission
if (OS::time() > (_announces_last_checked + _announces_check_interval)) {
//p for destination_hash in Transport.announce_table:
for (auto& [destination_hash, announce_entry] : _announce_table) {
//for (auto& pair : _announce_table) {
// const auto& destination_hash = pair.first;
// auto& announce_entry = pair.second;
//TRACE("[0] announce entry data size: " + std::to_string(announce_entry._packet.data().size()));
//p announce_entry = Transport.announce_table[destination_hash]
if (announce_entry._retries > 0 && announce_entry._retries >= Type::Transport::LOCAL_REBROADCASTS_MAX) {
TRACE("Completed announce processing for " + destination_hash.toHex() + ", local rebroadcast limit reached");
// CBA OK to modify collection here since we're immediately exiting iteration
_announce_table.erase(destination_hash);
break;
}
else if (announce_entry._retries > Type::Transport::PATHFINDER_R) {
TRACE("Completed announce processing for " + destination_hash.toHex() + ", retry limit reached");
// CBA OK to modify collection here since we're immediately exiting iteration
_announce_table.erase(destination_hash);
break;
}
else {
if (OS::time() > announce_entry._retransmit_timeout) {
TRACE("Performing announce processing for " + destination_hash.toHex() + "...");
announce_entry._retransmit_timeout = OS::time() + Type::Transport::PATHFINDER_G + Type::Transport::PATHFINDER_RW;
announce_entry._retries += 1;
//p packet = announce_entry[5]
//p block_rebroadcasts = announce_entry[7]
//p attached_interface = announce_entry[8]
Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
if (announce_entry._block_rebroadcasts) {
announce_context = Type::Packet::PATH_RESPONSE;
}
//p announce_data = packet.data
Identity announce_identity(Identity::recall(announce_entry._packet.destination_hash()));
//Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown");
//announce_destination.hash(announce_entry._packet.destination_hash());
Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, announce_entry._packet.destination_hash());
//P announce_destination.hexhash = announce_destination.hash.hex()
//if (announce_entry._attached_interface) {
//TRACE("[1] interface is valid");
//TRACE("[1] interface: " + announce_entry._attached_interface.debugString());
//TRACE("[1] interface: " + announce_entry._attached_interface.toString());
//}
Packet new_packet(
announce_destination,
//{Type::NONE},
announce_entry._attached_interface,
//{Type::NONE},
announce_entry._packet.data(),
Type::Packet::ANNOUNCE,
announce_context,
Type::Transport::TRANSPORT,
Type::Packet::HEADER_2,
Transport::_identity.hash(),
true,
announce_entry._packet.context_flag()
);
new_packet.hops(announce_entry._hops);
if (announce_entry._block_rebroadcasts) {
DEBUG("Rebroadcasting announce as path response for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
}
else {
DEBUG("Rebroadcasting announce for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
}
outgoing.push_back(new_packet);
// This handles an edge case where a peer sends a past
// request for a destination just after an announce for
// said destination has arrived, but before it has been
// rebroadcast locally. In such a case the actual announce
// is temporarily held, and then reinserted when the path
// request has been served to the peer.
//p if destination_hash in Transport.held_announces:
auto iter =_held_announces.find(destination_hash);
if (iter != _held_announces.end()) {
//p held_entry = Transport.held_announces.pop(destination_hash)
auto held_entry = (*iter).second;
_held_announces.erase(iter);
//p Transport.announce_table[destination_hash] = held_entry
//_announce_table[destination_hash] = held_entry;
//_announce_table.insert_or_assign({destination_hash, held_entry});
_announce_table.erase(destination_hash);
// CBA ACCUMULATES
_announce_table.insert({destination_hash, held_entry});
DEBUG("Reinserting held announce into table");
// CBA Must break after erase to avoid iterator invalidation
// (same pattern as the other two erases above in this loop)
break;
}
}
}
}
_announces_last_checked = OS::time();
}
// Cull the packet hashlist if it has reached its max size
if (_packet_hashlist.size() > _hashlist_maxsize) {
std::set<Bytes>::iterator iter = _packet_hashlist.begin();
std::advance(iter, _packet_hashlist.size() - _hashlist_maxsize);
_packet_hashlist.erase(_packet_hashlist.begin(), iter);
}
// Cull the path request tags list if it has reached its max size
if (_discovery_pr_tags.size() > _max_pr_tags) {
std::set<Bytes>::iterator iter = _discovery_pr_tags.begin();
std::advance(iter, _discovery_pr_tags.size() - _max_pr_tags);
_discovery_pr_tags.erase(_discovery_pr_tags.begin(), iter);
}
if (OS::time() > (_tables_last_culled + _tables_cull_interval)) {
// CBA Disabled following since we're calling immediately after adding to path table now
// Cull the path table if it has reached its max size
//cull_path_table();
// Cull the reverse table according to timeout
std::vector<Bytes> stale_reverse_entries;
for (const auto& [packet_hash, reverse_entry] : _reverse_table) {
if (OS::time() > (reverse_entry._timestamp + REVERSE_TIMEOUT)) {
stale_reverse_entries.push_back(packet_hash);
}
}
// Cull the link table according to timeout
std::vector<Bytes> stale_links;
for (const auto& [link_id, link_entry] : _link_table) {
if (link_entry._validated) {
if (OS::time() > (link_entry._timestamp + LINK_TIMEOUT)) {
stale_links.push_back(link_id);
}
}
else {
if (OS::time() > link_entry._proof_timeout) {
stale_links.push_back(link_id);
double last_path_request = 0.0;
const auto& iter = _path_requests.find(link_entry._destination_hash);
if (iter != _path_requests.end()) {
last_path_request = (*iter).second;
}
uint8_t lr_taken_hops = link_entry._hops;
bool path_request_throttle = (OS::time() - last_path_request) < PATH_REQUEST_MI;
bool path_request_conditions = false;
// If the path has been invalidated between the time of
// making the link request and now, try to rediscover it
if (!has_path(link_entry._destination_hash)) {
DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and path is now missing");
path_request_conditions = true;
}
// If this link request was originated from a local client
// attempt to rediscover a path to the destination, if this
// has not already happened recently.
else if (!path_request_throttle && lr_taken_hops == 0) {
DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted local client link was never established");
path_request_conditions = true;
}
// If the link destination was previously only 1 hop
// away, this likely means that it was local to one
// of our interfaces, and that it roamed somewhere else.
// In that case, try to discover a new path.
else if (!path_request_throttle && hops_to(link_entry._destination_hash) == 1) {
DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and destination was previously local to an interface on this instance");
path_request_conditions = true;
}
// If the link destination was previously only 1 hop
// away, this likely means that it was local to one
// of our interfaces, and that it roamed somewhere else.
// In that case, try to discover a new path.
else if ( !path_request_throttle and lr_taken_hops == 1) {
DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and link initiator is local to an interface on this instance");
path_request_conditions = true;
}
if (path_request_conditions) {
if (path_requests.count(link_entry._destination_hash) == 0) {
// CBA ACCUMULATES
path_requests.insert(link_entry._destination_hash);
}
if (!Reticulum::transport_enabled()) {
// Drop current path if we are not a transport instance, to
// allow using higher-hop count paths or reused announces
// from newly adjacent transport instances.
expire_path(link_entry._destination_hash);
}
}
}
}
}
// Cull the path table
std::vector<Bytes> stale_paths;
for (const auto& [destination_hash, destination_entry] : _destination_table) {
const Interface& attached_interface = destination_entry.receiving_interface();
double destination_expiry;
if (attached_interface && attached_interface.mode() == Type::Interface::MODE_ACCESS_POINT) {
destination_expiry = destination_entry._timestamp + AP_PATH_TIME;
}
else if (attached_interface && attached_interface.mode() == Type::Interface::MODE_ROAMING) {
destination_expiry = destination_entry._timestamp + ROAMING_PATH_TIME;
}
else {
destination_expiry = destination_entry._timestamp + DESTINATION_TIMEOUT;
}
if (OS::time() > destination_expiry) {
stale_paths.push_back(destination_hash);
DEBUG("Path to " + destination_hash.toHex() + " timed out and was removed");
}
else if (_interfaces.count(attached_interface.get_hash()) == 0) {
stale_paths.push_back(destination_hash);
DEBUG("Path to " + destination_hash.toHex() + " was removed since the attached interface no longer exists");
}
}
// Cull the pending discovery path requests table
std::vector<Bytes> stale_discovery_path_requests;
for (const auto& [destination_hash, path_entry] : _discovery_path_requests) {
if (OS::time() > path_entry._timeout) {
stale_discovery_path_requests.push_back(destination_hash);
DEBUG("Waiting path request for " + destination_hash.toString() + " timed out and was removed");
}
}
// Cull the tunnel table
count = 0;
std::vector<Bytes> stale_tunnels;
for (const auto& [tunnel_id, tunnel_entry] : _tunnels) {
if (OS::time() > tunnel_entry._expires) {
stale_tunnels.push_back(tunnel_id);
TRACE("Tunnel " + tunnel_id.toHex() + " timed out and was removed");
}
else {
std::vector<Bytes> stale_tunnel_paths;
for (const auto& [destination_hash, destination_entry] : tunnel_entry._serialised_paths) {
if (OS::time() > (destination_entry._timestamp + DESTINATION_TIMEOUT)) {
stale_tunnel_paths.push_back(destination_hash);
TRACE("Tunnel path to " + destination_hash.toHex() + " timed out and was removed");
}
}
//for (const auto& destination_hash : stale_tunnel_paths) {
for (const Bytes& destination_hash : stale_tunnel_paths) {
const_cast<TunnelEntry&>(tunnel_entry)._serialised_paths.erase(destination_hash);
++count;
}
}
}
if (count > 0) {
TRACE("Removed " + std::to_string(count) + " tunnel paths");
}
remove_reverse_entries(stale_reverse_entries);
remove_links(stale_links);
remove_paths(stale_paths);
remove_discovery_path_requests(stale_discovery_path_requests);
remove_tunnels(stale_tunnels);
//#ifndef NDEBUG
dump_stats();
//#endif
_tables_last_culled = OS::time();
}
// CBA Periodically persist data
//if (OS::time() > (_last_saved + _save_interval)) {
// persist_data();
// _last_saved = OS::time();
//}
}
else {
// Transport jobs were locked, do nothing
//p pass
}
}
catch (std::exception& e) {
ERROR("An exception occurred while running Transport jobs.");
ERRORF("The contained exception was: %s", e.what());
}
_jobs_running = false;
// CBA send announce retransmission packets
for (auto& packet : outgoing) {
packet.send();
}
// CBA send link-related path requests
for (auto& destination_hash : path_requests) {
request_path(destination_hash);
}
}
/*static*/ void Transport::transmit(Interface& interface, const Bytes& raw) {
TRACE("Transport::transmit()");
// CBA
if (_callbacks._transmit_packet) {
try {
_callbacks._transmit_packet(raw, interface);
}
catch (std::exception& e) {
DEBUG("Error while executing transmit packet callback. The contained exception was: " + std::string(e.what()));
}
}
try {
//if hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
if (interface.ifac_identity()) {
// TODO
/*p
// Calculate packet access code
ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:]
// Generate mask
mask = RNS.Cryptography.hkdf(
length=len(raw)+interface.ifac_size,
derive_from=ifac,
salt=interface.ifac_key,
context=None,
)
// Set IFAC flag
new_header = bytes([raw[0] | 0x80, raw[1]])
// Assemble new payload with IFAC
new_raw = new_header+ifac+raw[2:]
// Mask payload
i = 0; masked_raw = b""
for byte in new_raw:
if i == 0:
// Mask first header byte, but make sure the
// IFAC flag is still set
masked_raw += bytes([byte ^ mask[i] | 0x80])
elif i == 1 or i > interface.ifac_size+1:
// Mask second header byte and payload
masked_raw += bytes([byte ^ mask[i]])
else:
// Don't mask the IFAC itself
masked_raw += bytes([byte])
i += 1
// Send it
interface.on_outgoing(masked_raw)
*/
}
else {
interface.send_outgoing(raw);
}
}
catch (std::exception& e) {
ERROR("Error while transmitting on " + interface.toString() + ". The contained exception was: " + e.what());
}
}
/*static*/ bool Transport::outbound(Packet& packet) {
TRACE("Transport::outbound()");
++_packets_sent;
if (!packet.destination()) {
//throw std::invalid_argument("Can not send packet with no destination.");
ERROR("Can not send packet with no destination");
return false;
}
TRACE("Transport::outbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops()));
while (_jobs_running) {
TRACE("Transport::outbound: sleeping...");
OS::sleep(0.0005);
}
_jobs_locked = true;
bool sent = false;
double outbound_time = OS::time();
// Check if we have a known path for the destination in the path table
//if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination.type != RNS.Destination.PLAIN and packet.destination.type != RNS.Destination.GROUP and packet.destination_hash in Transport.destination_table:
if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.destination().type() != Type::Destination::PLAIN && packet.destination().type() != Type::Destination::GROUP && _destination_table.find(packet.destination_hash()) != _destination_table.end()) {
TRACE("Transport::outbound: Path to destination is known");
//outbound_interface = Transport.destination_table[packet.destination_hash][5]
DestinationEntry destination_entry = (*_destination_table.find(packet.destination_hash())).second;
Interface outbound_interface = destination_entry.receiving_interface();
// If there's more than one hop to the destination, and we know
// a path, we insert the packet into transport by adding the next
// transport nodes address to the header, and modifying the flags.
// This rule applies both for "normal" transport, and when connected
// to a local shared Reticulum instance.
//if Transport.destination_table[packet.destination_hash][2] > 1:
if (destination_entry._hops > 1) {
TRACE("Forwarding packet to next closest interface...");
if (packet.header_type() == Type::Packet::HEADER_1) {
// Insert packet into transport
//new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
uint8_t new_flags = (Type::Packet::HEADER_2) << 6 | (Type::Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111);
// CBA RESERVE
//Bytes new_raw;
Bytes new_raw(512);
//new_raw = struct.pack("!B", new_flags)
new_raw << new_flags;
//new_raw += packet.raw[1:2]
new_raw << packet.raw().mid(1,1);
//new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw << destination_entry._received_from;
//new_raw += packet.raw[2:]
new_raw << packet.raw().mid(2);
transmit(outbound_interface, new_raw);
//_destination_table[packet.destination_hash][0] = time.time()
destination_entry._timestamp = OS::time();
sent = true;
}
}
// In the special case where we are connected to a local shared
// Reticulum instance, and the destination is one hop away, we
// also add transport headers to inject the packet into transport
// via the shared instance. Normally a packet for a destination
// one hop away would just be broadcast directly, but since we
// are "behind" a shared instance, we need to get that instance
// to transport it onto the network.
//elif Transport.destination_table[packet.destination_hash][2] == 1 and Transport.owner.is_connected_to_shared_instance:
else if (destination_entry._hops == 1 && _owner.is_connected_to_shared_instance()) {
TRACE("Transport::outbound: Sending packet for directly connected interface to shared instance...");
if (packet.header_type() == Type::Packet::HEADER_1) {
// Insert packet into transport
//new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
uint8_t new_flags = (Type::Packet::HEADER_2) << 6 | (Type::Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111);
// CBA RESERVE
//Bytes new_raw;
Bytes new_raw(512);
//new_raw = struct.pack("!B", new_flags)
new_raw << new_flags;
//new_raw += packet.raw[1:2]
new_raw << packet.raw().mid(1, 1);
//new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw << destination_entry._received_from;
//new_raw += packet.raw[2:]
new_raw << packet.raw().mid(2);
transmit(outbound_interface, new_raw);
//Transport.destination_table[packet.destination_hash][0] = time.time()
destination_entry._timestamp = OS::time();
sent = true;
}
}
// If none of the above applies, we know the destination is
// directly reachable, and also on which interface, so we
// simply transmit the packet directly on that one.
else {
TRACE("Transport::outbound: Sending packet over directly connected interface...");
transmit(outbound_interface, packet.raw());
sent = true;
}
}
// If we don't have a known path for the destination, we'll
// broadcast the packet on all outgoing interfaces, or the
// just the relevant interface if the packet has an attached
// interface, or belongs to a link.
else {
TRACE("Transport::outbound: Path to destination is unknown");
bool stored_hash = false;
#if defined(INTERFACES_SET)
for (const Interface& interface : _interfaces) {
#elif defined(INTERFACES_LIST)
for (Interface& interface : _interfaces) {
#elif defined(INTERFACES_MAP)
for (auto& [hash, interface] : _interfaces) {
#endif
TRACE("Transport::outbound: Checking interface " + interface.toString());
if (interface.OUT()) {
bool should_transmit = true;
if (packet.destination().type() == Type::Destination::LINK) {
if (!packet.destination_link()) throw std::invalid_argument("Packet is not associated with a Link");
if (packet.destination_link().status() == Type::Link::CLOSED) {
TRACE("Transport::outbound: Pscket destination is link-closed, not transmitting");
should_transmit = false;
}
// CBA Bug? Destination has no member attached_interface
//z if (interface != packet.destination().attached_interface()) {
//z should_transmit = false;
//z }
}
if (packet.attached_interface() && interface != packet.attached_interface()) {
TRACE("Transport::outbound: Packet has wrong attached interface, not transmitting");
should_transmit = false;
}
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
if (!packet.attached_interface()) {
TRACE("Transport::outbound: Packet has no attached interface");
if (interface.mode() == Type::Interface::MODE_ACCESS_POINT) {
TRACE("Blocking announce broadcast on " + interface.toString() + " due to AP mode");
should_transmit = false;
}
else if (interface.mode() == Type::Interface::MODE_ROAMING) {
//local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
//Destination local_destination({Type::NONE});
#if defined(DESTINATIONS_SET)
bool found_local = false;
for (auto& destination : _destinations) {
if (destination.hash() == packet.destination_hash()) {
//local_destination = destination;
found_local = true;
break;
}
}
//if local_destination != None:
//if (local_destination) {
if (found_local) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(packet.destination_hash());
//if (iter != _destinations.end()) {
// local_destination = (*iter).second;
//}
if (iter != _destinations.end()) {
#endif
TRACE("Allowing announce broadcast on roaming-mode interface from instance-local destination");
}
else {
const Interface& from_interface = next_hop_interface(packet.destination_hash());
//if from_interface == None or not hasattr(from_interface, "mode"):
if (!from_interface || from_interface.mode() == Type::Interface::MODE_NONE) {
should_transmit = false;
if (!from_interface) {
TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist");
}
else if (from_interface.mode() == Type::Interface::MODE_NONE) {
TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured");
}
}
else {
if (from_interface.mode() == Type::Interface::MODE_ROAMING) {
TRACE("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface");
should_transmit = false;
}
else if (from_interface.mode() == Type::Interface::MODE_BOUNDARY) {
TRACE("Blocking announce broadcast on " + interface.toString() + " due to boundary-mode next-hop interface");
should_transmit = false;
}
}
}
}
else if (interface.mode() == Type::Interface::MODE_BOUNDARY) {
//local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
// next and filter pattern?
// next(iterable, default)
// list comprehension: [x for x in xyz if x in a]
// CBA TODO confirm that above pattern just selects the first matching destination
#if defined(DESTINATIONS_SET)
//Destination local_destination({Type::Destination::NONE});
bool found_local = false;
for (auto& destination : _destinations) {
if (destination.hash() == packet.destination_hash()) {
//local_destination = destination;
found_local = true;
break;
}
}
//if local_destination != None:
//if (local_destination) {
if (found_local) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(packet.destination_hash());
if (iter != _destinations.end()) {
#endif
TRACE("Allowing announce broadcast on boundary-mode interface from instance-local destination");
}
else {
const Interface& from_interface = next_hop_interface(packet.destination_hash());
if (!from_interface || from_interface.mode() == Type::Interface::MODE_NONE) {
should_transmit = false;
if (!from_interface) {
TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist");
}
else if (from_interface.mode() == Type::Interface::MODE_NONE) {
TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured");
}
}
else {
if (from_interface.mode() == Type::Interface::MODE_ROAMING) {
TRACE("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface");
should_transmit = false;
}
}
}
}
else {
// Currently, annouces originating locally are always
// allowed, and do not conform to bandwidth caps.
// TODO: Rethink whether this is actually optimal.
if (packet.hops() > 0) {
// TODO
/*p
if not hasattr(interface, "announce_cap"):
interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
if not hasattr(interface, "announce_allowed_at"):
interface.announce_allowed_at = 0
if not hasattr(interface, "announce_queue"):
interface.announce_queue = []
*/
bool queued_announces = (interface.announce_queue().size() > 0);
if (!queued_announces && outbound_time > interface.announce_allowed_at()) {
uint16_t wait_time = 0;
if (interface.bitrate() > 0 && interface.announce_cap() > 0) {
uint16_t tx_time = (packet.raw().size() * 8) / interface.bitrate();
wait_time = (tx_time / interface.announce_cap());
}
#if defined(INTERFACES_SET)
const_cast<Interface&>(interface).announce_allowed_at(outbound_time + wait_time);
#else
interface.announce_allowed_at(outbound_time + wait_time);
#endif
}
else {
should_transmit = false;
if (interface.announce_queue().size() < Type::Reticulum::MAX_QUEUED_ANNOUNCES) {
bool should_queue = true;
for (auto& entry : interface.announce_queue()) {
if (entry._destination == packet.destination_hash()) {
uint64_t emission_timestamp = announce_emitted(packet);
should_queue = false;
if (emission_timestamp > entry._emitted) {
entry._time = outbound_time;
entry._hops = packet.hops();
entry._emitted = emission_timestamp;
entry._raw = packet.raw();
}
break;
}
}
if (should_queue) {
RNS::AnnounceEntry entry(
packet.destination_hash(),
outbound_time,
packet.hops(),
announce_emitted(packet),
packet.raw()
);
queued_announces = (interface.announce_queue().size() > 0);
#if defined(INTERFACES_SET)
const_cast<Interface&>(interface).add_announce(entry);
#else
// CBA ACCUMULATES
interface.add_announce(entry);
#endif
if (!queued_announces) {
double wait_time = std::max(interface.announce_allowed_at() - OS::time(), (double)0);
// CBA TODO THREAD?
//z timer = threading.Timer(wait_time, interface.process_announce_queue)
//z timer.start()
if (wait_time < 1000) {
TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string((int)wait_time) + " ms");
}
else {
TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string(OS::round(wait_time/1000,1)) + " s");
}
}
else {
double wait_time = std::max(interface.announce_allowed_at() - OS::time(), (double)0);
if (wait_time < 1000) {
TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string((int)wait_time) + " ms");
}
else {
TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string(OS::round(wait_time/1000,1)) + " s");
}
}
}
}
else {
//p pass
}
}
}
else {
//p pass
}
}
}
}
if (should_transmit) {
TRACE("Transport::outbound: Packet transmission allowed");
if (!stored_hash) {
// CBA ACCUMULATES
_packet_hashlist.insert(packet.packet_hash());
stored_hash = true;
}
// TODO: Re-evaluate potential for blocking
// def send_packet():
// Transport.transmit(interface, packet.raw)
// thread = threading.Thread(target=send_packet)
// thread.daemon = True
// thread.start()
#if defined(INTERFACES_SET)
transmit(const_cast<Interface&>(interface), packet.raw());
#else
transmit(interface, packet.raw());
#endif
sent = true;
}
else {
TRACE("Transport::outbound: Packet transmission refused");
}
}
}
}
if (sent) {
packet.sent(true);
packet.sent_at(OS::time());
// Don't generate receipt if it has been explicitly disabled
if (packet.create_receipt() &&
// Only generate receipts for DATA packets
packet.packet_type() == Type::Packet::DATA &&
// Don't generate receipts for PLAIN destinations
packet.destination().type() != Type::Destination::PLAIN &&
// Don't generate receipts for link-related packets
!(packet.context() >= Type::Packet::KEEPALIVE && packet.context() <= Type::Packet::LRPROOF) &&
// Don't generate receipts for resource packets
!(packet.context() >= Type::Packet::RESOURCE && packet.context() <= Type::Packet::RESOURCE_RCL)) {
PacketReceipt receipt(packet);
packet.receipt(receipt);
// CBA ACCUMULATES
_receipts.push_back(receipt);
}
cache_packet(packet);
}
_jobs_locked = false;
return sent;
}
/*static*/ bool Transport::packet_filter(const Packet& packet) {
// TODO: Think long and hard about this.
// Is it even strictly necessary with the current
// transport rules?
if (packet.context() == Type::Packet::KEEPALIVE) {
return true;
}
if (packet.context() == Type::Packet::RESOURCE_REQ) {
return true;
}
if (packet.context() == Type::Packet::RESOURCE_PRF) {
return true;
}
if (packet.context() == Type::Packet::RESOURCE) {
return true;
}
if (packet.context() == Type::Packet::CACHE_REQUEST) {
return true;
}
if (packet.context() == Type::Packet::CHANNEL) {
return true;
}
if (packet.destination_type() == Type::Destination::PLAIN) {
if (packet.packet_type() != Type::Packet::ANNOUNCE) {
if (packet.hops() > 1) {
DEBUG("Dropped PLAIN packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops");
return false;
}
else {
return true;
}
}
else {
DEBUG("Dropped invalid PLAIN announce packet");
return false;
}
}
if (packet.destination_type() == Type::Destination::GROUP) {
if (packet.packet_type() != Type::Packet::ANNOUNCE) {
if (packet.hops() > 1) {
DEBUG("Dropped GROUP packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops");
return false;
}
else {
return true;
}
}
else {
DEBUG("Dropped invalid GROUP announce packet");
return false;
}
}
if (_packet_hashlist.find(packet.packet_hash()) == _packet_hashlist.end()) {
TRACE("Transport::packet_filter: packet not previously seen");
return true;
}
else {
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
if (packet.destination_type() == Type::Destination::SINGLE) {
TRACE("Transport::packet_filter: packet previously seen but is SINGLE ANNOUNCE");
return true;
}
else {
DEBUG("Dropped invalid announce packet");
return false;
}
}
}
DEBUG("Filtered packet with hash " + packet.packet_hash().toHex());
return false;
}
/*static*/ void Transport::inbound(const Bytes& raw, const Interface& interface /*= {Type::NONE}*/) {
TRACEF("Transport::inbound: received %d bytes", raw.size());
++_packets_received;
// CBA
if (_callbacks._receive_packet) {
try {
_callbacks._receive_packet(raw, interface);
}
catch (std::exception& e) {
DEBUG("Error while executing receive packet callback. The contained exception was: " + std::string(e.what()));
}
}
// TODO
/*p
// If interface access codes are enabled,
// we must authenticate each packet.
//if len(raw) > 2:
if (raw.size() > 2) {
if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
// Check that IFAC flag is set
if raw[0] & 0x80 == 0x80:
if len(raw) > 2+interface.ifac_size:
// Extract IFAC
ifac = raw[2:2+interface.ifac_size]
// Generate mask
mask = RNS.Cryptography.hkdf(
length=len(raw),
derive_from=ifac,
salt=interface.ifac_key,
context=None,
)
// Unmask payload
i = 0; unmasked_raw = b""
for byte in raw:
if i <= 1 or i > interface.ifac_size+1:
// Unmask header bytes and payload
unmasked_raw += bytes([byte ^ mask[i]])
else:
// Don't unmask IFAC itself
unmasked_raw += bytes([byte])
i += 1
raw = unmasked_raw
// Unset IFAC flag
new_header = bytes([raw[0] & 0x7f, raw[1]])
// Re-assemble packet
new_raw = new_header+raw[2+interface.ifac_size:]
// Calculate expected IFAC
expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:]
// Check it
if ifac == expected_ifac:
raw = new_raw
else:
return
else:
return
else:
// If the IFAC flag is not set, but should be,
// drop the packet.
return
else:
// If the interface does not have IFAC enabled,
// check the received packet IFAC flag.
if raw[0] & 0x80 == 0x80:
// If the flag is set, drop the packet
return
}
else {
return;
}
*/
while (_jobs_running) {
TRACE("Transport::inbound: sleeping...");
OS::sleep(0.0005);
}
if (!_identity) {
WARNING("Transport::inbound: No identity!");
return;
}
_jobs_locked = true;
Packet packet(RNS::Destination(RNS::Type::NONE), raw);
if (!packet.unpack()) {
WARNING("Transport::inbound: Packet unpack failed!");
return;
}
#ifndef NDEBUG
TRACE("Transport::inbound: packet: " + packet.debugString());
#endif
TRACE("Transport::inbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops()));
packet.receiving_interface(interface);
packet.hops(packet.hops() + 1);
// TODO
/*p
if (interface) {
if hasattr(interface, "r_stat_rssi"):
if interface.r_stat_rssi != None:
packet.rssi = interface.r_stat_rssi
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_rssi_cache.pop()
if hasattr(interface, "r_stat_snr"):
if interface.r_stat_rssi != None:
packet.snr = interface.r_stat_snr
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_snr_cache.pop()
}
*/
if (_local_client_interfaces.size() > 0) {
if (is_local_client_interface(interface)) {
packet.hops(packet.hops() - 1);
}
}
else if (interface_to_shared_instance(interface)) {
packet.hops(packet.hops() - 1);
}
//if (packet_filter(packet)) {
// CBA
bool accept = true;
if (_callbacks._filter_packet) {
try {
accept = _callbacks._filter_packet(packet);
}
catch (std::exception& e) {
DEBUG("Error while executing filter packet callback. The contained exception was: " + std::string(e.what()));
}
}
if (accept) {
accept = packet_filter(packet);
}
if (accept) {
TRACE("Transport::inbound: Packet accepted by filter");
// BOUNDARY MODE: Gate backbone traffic using two whitelists.
// Whitelist 1: local device addresses (LoRa + LocalTCP)
// Whitelist 2: addresses mentioned in packets from local devices
#ifdef BOUNDARY_MODE
{
bool is_backbone = is_backbone_interface(packet.receiving_interface());
if (is_backbone) {
bool allowed = false;
// Whitelist 1: destination is a local device
if (_boundary_local_addresses.find(packet.destination_hash()) != _boundary_local_addresses.end()) {
allowed = true;
}
// Whitelist 2: destination was mentioned by a local device
else if (_boundary_mentioned_addresses.find(packet.destination_hash()) != _boundary_mentioned_addresses.end()) {
allowed = true;
}
// Allow return traffic: proofs routed via reverse_table
// (destination is the packet hash of a packet we forwarded)
else if (_reverse_table.find(packet.destination_hash()) != _reverse_table.end()) {
allowed = true;
}
// Allow return traffic: link proofs and link data routed via link_table
// (destination is the link_id of a link we're transporting)
else if (_link_table.find(packet.destination_hash()) != _link_table.end()) {
allowed = true;
}
// Allow packets addressed to our own control destinations
// (e.g. path request handler) so backbone nodes can discover
// paths to local devices through us
else if (_control_hashes.find(packet.destination_hash()) != _control_hashes.end()) {
allowed = true;
}
// Allow packets addressed to our own registered destinations
else if (_destinations.find(packet.destination_hash()) != _destinations.end()) {
allowed = true;
}
if (!allowed) {
return;
}
}
else {
// Packet from local interface: add its destination to Whitelist 2
_boundary_mentioned_addresses.insert(packet.destination_hash());
}
}
#endif
// CBA ACCUMULATES
_packet_hashlist.insert(packet.packet_hash());
cache_packet(packet);
// Check special conditions for local clients connected
// through a shared Reticulum instance
//p from_local_client = (packet.receiving_interface in Transport.local_client_interfaces)
bool from_local_client = (_local_client_interfaces.find(packet.receiving_interface()) != _local_client_interfaces.end());
//p for_local_client = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.destination_table and Transport.destination_table[packet.destination_hash][2] == 0)
//p for_local_client_link = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][4] in Transport.local_client_interfaces)
//p for_local_client_link |= (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][2] in Transport.local_client_interfaces)
// If packet is anything besides ANNOUNCE then determine if it's destinated for a local destination or link
bool for_local_client = false;
bool for_local_client_link = false;
if (packet.packet_type() != Type::Packet::ANNOUNCE) {
auto destination_iter = _destination_table.find(packet.destination_hash());
if (destination_iter != _destination_table.end()) {
DestinationEntry destination_entry = (*destination_iter).second;
if (destination_entry._hops == 0) {
// Destined for a local destination
for_local_client = true;
}
}
auto link_iter = _link_table.find(packet.destination_hash());
if (link_iter != _link_table.end()) {
LinkEntry link_entry = (*link_iter).second;
if (_local_client_interfaces.find(link_entry._receiving_interface) != _local_client_interfaces.end()) {
// Destined for a local link
for_local_client_link = true;
}
if (_local_client_interfaces.find(link_entry._outbound_interface) != _local_client_interfaces.end()) {
// Destined for a local link
for_local_client_link = true;
}
}
}
// Determine if packet is proof for local destination???
//p proof_for_local_client = (packet.destination_hash in Transport.reverse_table) and (Transport.reverse_table[packet.destination_hash][0] in Transport.local_client_interfaces)
bool proof_for_local_client = false;
auto reverse_iter = _reverse_table.find(packet.destination_hash());
if (reverse_iter != _reverse_table.end()) {
ReverseEntry reverse_entry = (*reverse_iter).second;
if (_local_client_interfaces.find(reverse_entry._receiving_interface) != _local_client_interfaces.end()) {
// Proof for local destination???
proof_for_local_client = true;
}
}
// Plain broadcast packets from local clients are sent
// directly on all attached interfaces, since they are
// never injected into transport.
// If packet is not destined for a local transport-specific destination
if (_control_hashes.find(packet.destination_hash()) == _control_hashes.end()) {
// If packet is destination type PLAIN and transport type BROADCAST
if (packet.destination_type() == Type::Destination::PLAIN && packet.transport_type() == Type::Transport::BROADCAST) {
// Send to all interfaces except the one the packet was recieved on
if (from_local_client) {
#if defined(INTERFACES_SET)
for (const Interface& interface : _interfaces) {
#elif defined(INTERFACES_LIST)
for (Interface& interface : _interfaces) {
#elif defined(INTERFACES_MAP)
for (auto& [hash, interface] : _interfaces) {
#endif
if (interface != packet.receiving_interface()) {
TRACE("Transport::inbound: Broadcasting packet on " + interface.toString());
#if defined(INTERFACES_SET)
transmit(const_cast<Interface&>(interface), packet.raw());
#else
transmit(interface, packet.raw());
#endif
}
}
}
// If the packet was not from a local client, send
// it directly to all local clients
else {
for (const Interface& interface : _local_client_interfaces) {
TRACE("Transport::inbound: Broadcasting packet on " + interface.toString());
transmit(const_cast<Interface&>(interface), packet.raw());
}
}
}
}
////////////////////////////////
// TRANSPORT HANDLING
////////////////////////////////
// General transport handling. Takes care of directing
// packets according to transport tables and recording
// entries in reverse and link tables.
if (Reticulum::transport_enabled() || from_local_client || for_local_client || for_local_client_link) {
TRACE("Transport::inbound: Performing general transport handling");
// If there is no transport id, but the packet is
// for a local client, we generate the transport
// id (it was stripped on the previous hop, since
// we "spoof" the hop count for clients behind a
// shared instance, so they look directly reach-
// able), and reinsert, so the normal transport
// implementation can handle the packet.
if (!packet.transport_id() && for_local_client) {
TRACE("Transport::inbound: Regenerating transport id");
packet.transport_id(_identity.hash());
}
// If this is a cache request, and we can fullfill
// it, do so and stop processing. Otherwise resume
// normal processing.
if (packet.context() == Type::Packet::CACHE_REQUEST) {
if (cache_request_packet(packet)) {
TRACE("Transport::inbound: Cached packet");
return;
}
}
// If the packet is in transport, check whether we
// are the designated next hop, and process it
// accordingly if we are.
if (packet.transport_id() && packet.packet_type() != Type::Packet::ANNOUNCE) {
TRACE("Transport::inbound: Packet is in transport...");
if (packet.transport_id() == _identity.hash()) {
TRACE("Transport::inbound: We are designated next-hop");
auto destination_iter = _destination_table.find(packet.destination_hash());
if (destination_iter != _destination_table.end()) {
TRACE("Transport::inbound: Found next-hop path to destination");
DestinationEntry destination_entry = (*destination_iter).second;
Bytes next_hop = destination_entry._received_from;
uint8_t remaining_hops = destination_entry._hops;
// CBA RESERVE
//Bytes new_raw;
Bytes new_raw(512);
if (remaining_hops > 1) {
// Just increase hop count and transmit
//new_raw = packet.raw[0:1]
new_raw << packet.raw().left(1);
//new_raw += struct.pack("!B", packet.hops)
new_raw << packet.hops();
//new_raw += next_hop
new_raw << next_hop;
//new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
new_raw << packet.raw().mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
}
else if (remaining_hops == 1) {
// Strip transport headers and transmit
//new_flags = (RNS.Packet.HEADER_1) << 6 | (Transport.BROADCAST) << 4 | (packet.flags & 0b00001111)
uint8_t new_flags = (Type::Packet::HEADER_1) << 6 | (Type::Transport::BROADCAST) << 4 | (packet.flags() & 0b00001111);
//new_raw = struct.pack("!B", new_flags)
new_raw << new_flags;
//new_raw += struct.pack("!B", packet.hops)
new_raw << packet.hops();
//new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
new_raw << packet.raw().mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
}
else if (remaining_hops == 0) {
// Just increase hop count and transmit
//new_raw = packet.raw[0:1]
new_raw << packet.raw().left(1);
//new_raw += struct.pack("!B", packet.hops)
new_raw << packet.hops();
//new_raw += packet.raw[2:]
new_raw << packet.raw().mid(2);
}
Interface outbound_interface = destination_entry.receiving_interface();
if (packet.packet_type() == Type::Packet::LINKREQUEST) {
TRACE("Transport::inbound: Packet is next-hop LINKREQUEST");
double now = OS::time();
double proof_timeout = now + Type::Link::ESTABLISHMENT_TIMEOUT_PER_HOP * std::max((uint8_t)1, remaining_hops);
LinkEntry link_entry(
now,
next_hop,
outbound_interface,
remaining_hops,
packet.receiving_interface(),
packet.hops(),
packet.destination_hash(),
false,
proof_timeout
);
// CBA ACCUMULATES
_link_table.insert({Link::link_id_from_lr_packet(packet), link_entry});
}
else {
TRACE("Transport::inbound: Packet is next-hop other type");
ReverseEntry reverse_entry(
packet.receiving_interface(),
outbound_interface,
OS::time()
);
// CBA ACCUMULATES
_reverse_table.insert({packet.getTruncatedHash(), reverse_entry});
}
TRACE("Transport::outbound: Sending packet to next hop...");
#if defined(INTERFACES_SET)
transmit(const_cast<Interface&>(outbound_interface), new_raw);
#else
transmit(outbound_interface, new_raw);
#endif
destination_entry._timestamp = OS::time();
}
else {
#ifdef BOUNDARY_MODE
// BOUNDARY MODE: No path to destination. If packet came from
// a local device (non-backbone), request the path.
{
bool from_backbone = is_backbone_interface(packet.receiving_interface());
if (!from_backbone) {
DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local device packet. Requesting path.");
request_path(packet.destination_hash());
}
}
#else
// TODO: There should probably be some kind of REJECT
// mechanism here, to signal to the source that their
// expected path failed.
TRACE("Got packet in transport, but no known path to final destination " + packet.destination_hash().toHex() + ". Dropping packet.");
#endif
}
}
else {
TRACE("Transport::inbound: We are not designated next-hop so not transporting");
}
}
else {
TRACE("Transport::inbound: Either packet is announce or packet has no next-hop (possibly for a local destination)");
#ifdef BOUNDARY_MODE
// BOUNDARY MODE: If this packet came from a local interface and we
// have a path to the destination, wrap it with transport headers
// and forward it through the backbone as the first transport hop.
// Skip ANNOUNCE and PROOF packets — announces have their own handling,
// and link proofs (LRPROOF) are handled by the LRPROOF transport code.
// Also skip packets destined for locally-registered destinations
// (e.g. path request handler) — those must be processed locally.
bool is_local_destination = false;
#if defined(DESTINATIONS_MAP)
is_local_destination = (_destinations.find(packet.destination_hash()) != _destinations.end());
#elif defined(DESTINATIONS_SET)
for (auto& dest : _destinations) {
if (dest.hash() == packet.destination_hash()) { is_local_destination = true; break; }
}
#endif
if (!is_local_destination && packet.packet_type() != Type::Packet::ANNOUNCE && packet.packet_type() != Type::Packet::PROOF) {
bool is_from_backbone = is_backbone_interface(packet.receiving_interface());
if (!is_from_backbone) {
auto destination_iter = _destination_table.find(packet.destination_hash());
if (destination_iter != _destination_table.end()) {
DestinationEntry& dest_entry = (*destination_iter).second;
Bytes next_hop = dest_entry._received_from;
uint8_t remaining_hops = dest_entry._hops;
Interface outbound_interface = dest_entry.receiving_interface();
// Build outgoing packet based on remaining hops,
// mirroring standard transport forwarding logic.
Bytes new_raw(512);
if (remaining_hops > 1) {
// Multi-hop: wrap with HEADER_2/TRANSPORT,
// setting transport_id = next_hop (the next
// transport node's identity hash from announce).
uint8_t new_flags = (Type::Packet::HEADER_2) << 6
| (Type::Transport::TRANSPORT) << 4
| (packet.flags() & 0b00001111);
new_raw << new_flags;
new_raw << packet.hops();
new_raw << next_hop; // insert transport_id
new_raw << packet.raw().mid(2); // destination_hash + payload
}
else {
// Single hop (remaining_hops <= 1): destination is
// directly reachable. Send as HEADER_1/BROADCAST
// (no transport header), matching standard transport
// behaviour for final-hop delivery.
uint8_t new_flags = (Type::Packet::HEADER_1) << 6
| (Type::Transport::BROADCAST) << 4
| (packet.flags() & 0b00001111);
new_raw << new_flags;
new_raw << packet.hops();
new_raw << packet.raw().mid(2); // destination_hash + payload
}
// Create link_table or reverse_table entry for return path
if (packet.packet_type() == Type::Packet::LINKREQUEST) {
double now = OS::time();
double proof_timeout = now + Type::Link::ESTABLISHMENT_TIMEOUT_PER_HOP
* std::max((uint8_t)1, remaining_hops);
LinkEntry link_entry(
now, next_hop, outbound_interface, remaining_hops,
packet.receiving_interface(), packet.hops(),
packet.destination_hash(), false, proof_timeout
);
// Each LINKREQUEST gets its own entry (unique link_id)
_link_table.insert({Link::link_id_from_lr_packet(packet), link_entry});
}
else {
ReverseEntry reverse_entry(
packet.receiving_interface(), outbound_interface, OS::time()
);
_reverse_table.insert({packet.getTruncatedHash(), reverse_entry});
}
DEBUG("BOUNDARY: Forwarding local packet (" + std::to_string(remaining_hops) + " hops, " + std::to_string(new_raw.size()) + " bytes) to " + outbound_interface.toString() + " for " + packet.destination_hash().toHex());
transmit(outbound_interface, new_raw);
dest_entry._timestamp = OS::time();
}
else {
DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local packet. Requesting path.");
request_path(packet.destination_hash());
}
}
else {
// BOUNDARY MODE REVERSE: Packet came from backbone,
// check if destination is a local LoRa device and forward it.
if (_boundary_local_addresses.find(packet.destination_hash()) != _boundary_local_addresses.end()) {
auto destination_iter = _destination_table.find(packet.destination_hash());
if (destination_iter != _destination_table.end()) {
DestinationEntry& dest_entry = (*destination_iter).second;
Interface outbound_interface = dest_entry.receiving_interface();
// Create reverse_table entry so proof can get back
ReverseEntry reverse_entry(
packet.receiving_interface(), outbound_interface, OS::time()
);
_reverse_table.insert({packet.getTruncatedHash(), reverse_entry});
DEBUG("BOUNDARY: Forwarding backbone packet to local device for " + packet.destination_hash().toHex() + " via " + outbound_interface.toString());
transmit(outbound_interface, packet.raw());
dest_entry._timestamp = OS::time();
}
}
}
}
#endif
}
// Link transport handling. Directs packets according
// to entries in the link tables
if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.packet_type() != Type::Packet::LINKREQUEST && packet.context() != Type::Packet::LRPROOF) {
TRACE("Transport::inbound: Checking if packet is meant for link transport...");
auto link_iter = _link_table.find(packet.destination_hash());
if (link_iter != _link_table.end()) {
TRACE("Transport::inbound: Found link entry, handling link transport");
LinkEntry link_entry = (*link_iter).second;
// If receiving and outbound interface is
// the same for this link, direction doesn't
// matter, and we simply send the packet on.
Interface outbound_interface({Type::NONE});
if (link_entry._outbound_interface == link_entry._receiving_interface) {
// But check that taken hops matches one
// of the expectede values.
if (packet.hops() == link_entry._remaining_hops || packet.hops() == link_entry._hops) {
TRACE("Transport::inbound: Link inbound/outbound interfaes are same, transporting on same interface");
outbound_interface = link_entry._outbound_interface;
}
}
else {
// If interfaces differ, we transmit on
// the opposite interface of what the
// packet was received on.
if (packet.receiving_interface() == link_entry._outbound_interface) {
// Also check that expected hop count matches
if (packet.hops() == link_entry._remaining_hops) {
TRACE("Transport::inbound: Link transporting on inbound interface");
outbound_interface = link_entry._receiving_interface;
}
}
else if (packet.receiving_interface() == link_entry._receiving_interface) {
// Also check that expected hop count matches
if (packet.hops() == link_entry._hops) {
TRACE("Transport::inbound: Link transporting on outbound interface");
outbound_interface = link_entry._outbound_interface;
}
}
}
if (outbound_interface) {
TRACE("Transport::inbound: Transmitting link transport packet");
// CBA RESERVE
//Bytes new_raw;
Bytes new_raw(512);
//new_raw = packet.raw[0:1]
new_raw << packet.raw().left(1);
//new_raw += struct.pack("!B", packet.hops)
new_raw << packet.hops();
//new_raw += packet.raw[2:]
new_raw << packet.raw().mid(2);
transmit(outbound_interface, new_raw);
link_entry._timestamp = OS::time();
}
else {
//p pass
}
}
}
}
////////////////////////////////
// LOCAL HANDLING
////////////////////////////////
// Announce handling. Handles logic related to incoming
// announces, queueing rebroadcasts of these, and removal
// of queued announce rebroadcasts once handed to the next node.
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
TRACE("Transport::inbound: Packet is ANNOUNCE");
Bytes received_from;
//p local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
#if defined(DESTINATIONS_SET)
//Destination local_destination({Type::NONE});
bool found_local = false;
for (auto& destination : _destinations) {
if (destination.hash() == packet.destination_hash()) {
//local_destination = destination;
found_local = true;
break;
}
}
//if local_destination == None and RNS.Identity.validate_announce(packet):
//if (!local_destination && Identity::validate_announce(packet)) {
if (!found_local && Identity::validate_announce(packet)) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(packet.destination_hash());
if (iter == _destinations.end() && Identity::validate_announce(packet)) {
#endif
TRACE("Transport::inbound: Packet is announce for non-local destination, processing...");
if (packet.transport_id()) {
received_from = packet.transport_id();
// Check if this is a next retransmission from
// another node. If it is, we're removing the
// announce in question from our pending table
if (Reticulum::transport_enabled() && _announce_table.count(packet.destination_hash()) > 0) {
//AnnounceEntry& announce_entry = _announce_table[packet.destination_hash()];
AnnounceEntry& announce_entry = (*_announce_table.find(packet.destination_hash())).second;
if ((packet.hops() - 1) == announce_entry._hops) {
DEBUG("Heard a local rebroadcast of announce for " + packet.destination_hash().toHex());
announce_entry._local_rebroadcasts += 1;
if (announce_entry._local_rebroadcasts >= LOCAL_REBROADCASTS_MAX) {
DEBUG("Max local rebroadcasts of announce for " + packet.destination_hash().toHex() + " reached, dropping announce from our table");
_announce_table.erase(packet.destination_hash());
}
}
if ((packet.hops() - 1) == (announce_entry._hops + 1) && announce_entry._retries > 0) {
double now = OS::time();
if (now < announce_entry._timestamp) {
DEBUG("Rebroadcasted announce for " + packet.destination_hash().toHex() + " has been passed on to another node, no further tries needed");
_announce_table.erase(packet.destination_hash());
}
}
}
}
else {
received_from = packet.destination_hash();
}
// Check if this announce should be inserted into
// announce and destination tables
bool should_add = false;
// First, check that the announce is not for a destination
// local to this system, and that hops are less than the max
// CBA TODO determine why packet destination hash is being searched in destinations again since we entered this logic becuase it did not exist above
//if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
#if defined(DESTINATIONS_SET)
bool found_local = false;
for (auto& destination : _destinations) {
if (destination.hash() == packet.destination_hash()) {
found_local = true;
break;
}
}
if (!found_local && packet.hops() < (PATHFINDER_M+1)) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(packet.destination_hash());
if (iter == _destinations.end() && packet.hops() < (PATHFINDER_M+1)) {
#endif
uint64_t announce_emitted = Transport::announce_emitted(packet);
//p random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
Bytes random_blob = packet.data().mid(Type::Identity::KEYSIZE/8 + Type::Identity::NAME_HASH_LENGTH/8, Type::Identity::RANDOM_HASH_LENGTH/8);
//p random_blobs = []
std::set<Bytes> empty_random_blobs;
std::set<Bytes>& random_blobs = empty_random_blobs;
auto iter = _destination_table.find(packet.destination_hash());
if (iter != _destination_table.end()) {
DestinationEntry destination_entry = (*iter).second;
//p random_blobs = Transport.destination_table[packet.destination_hash][4]
random_blobs = destination_entry._random_blobs;
// If we already have a path to the announced
// destination, but the hop count is equal or
// less, we'll update our tables.
if (packet.hops() <= destination_entry._hops) {
// Make sure we haven't heard the random
// blob before, so announces can't be
// replayed to forge paths.
// TODO: Check whether this approach works
// under all circumstances
//p if not random_blob in random_blobs:
if (random_blobs.find(random_blob) == random_blobs.end()) {
should_add = true;
}
else {
should_add = false;
}
}
else {
// If an announce arrives with a larger hop
// count than we already have in the table,
// ignore it, unless the path is expired, or
// the emission timestamp is more recent.
double now = OS::time();
double path_expires = destination_entry._expires;
uint64_t path_announce_emitted = 0;
for (const Bytes& path_random_blob : random_blobs) {
//p path_announce_emitted = max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big"))
path_announce_emitted = std::max(path_announce_emitted, OS::from_bytes_big_endian(path_random_blob.data() + 5, 5));
if (path_announce_emitted >= announce_emitted) {
break;
}
}
if (now >= path_expires) {
// We also check that the announce is
// different from ones we've already heard,
// to avoid loops in the network
if (random_blobs.find(random_blob) == random_blobs.end()) {
// TODO: Check that this ^ approach actually
// works under all circumstances
DEBUG("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce due to expired path");
should_add = true;
}
else {
should_add = false;
}
}
else {
if (announce_emitted > path_announce_emitted) {
if (random_blobs.find(random_blob) == random_blobs.end()) {
DEBUG("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce, since it was more recently emitted");
should_add = true;
}
else {
should_add = false;
}
}
}
}
}
else {
// If this destination is unknown in our table
// we should add it
should_add = true;
}
if (should_add) {
// BOUNDARY MODE: Prevent backbone echo from overwriting a local LoRa path
#ifdef BOUNDARY_MODE
{
auto existing_iter = _destination_table.find(packet.destination_hash());
if (existing_iter != _destination_table.end()) {
DestinationEntry& existing = existing_iter->second;
Interface existing_iface = existing.receiving_interface();
bool existing_is_lora = !is_backbone_interface(existing_iface);
bool new_is_backbone = is_backbone_interface(packet.receiving_interface());
if (existing_is_lora && new_is_backbone) {
DEBUG("BOUNDARY: Blocking backbone announce echo from overwriting local LoRa path for " + packet.destination_hash().toHex()
+ " (existing " + std::to_string(existing._hops) + " hops via LoRa, new " + std::to_string(packet.hops()) + " hops via backbone)");
should_add = false;
}
}
}
#endif
}
if (should_add) {
double now = OS::time();
bool rate_blocked = false;
// TODO
/*p
if packet.context != RNS.Packet.PATH_RESPONSE and packet.receiving_interface.announce_rate_target != None:
if not packet.destination_hash in Transport.announce_rate_table:
rate_entry = { "last": now, "rate_violations": 0, "blocked_until": 0, "timestamps": [now]}
Transport.announce_rate_table[packet.destination_hash] = rate_entry
else:
rate_entry = Transport.announce_rate_table[packet.destination_hash]
rate_entry["timestamps"].append(now)
while len(rate_entry["timestamps"]) > Transport.MAX_RATE_TIMESTAMPS:
rate_entry["timestamps"].pop(0)
current_rate = now - rate_entry["last"]
if now > rate_entry["blocked_until"]:
if current_rate < packet.receiving_interface.announce_rate_target:
rate_entry["rate_violations"] += 1
else:
rate_entry["rate_violations"] = std::max(0, rate_entry["rate_violations"]-1)
if rate_entry["rate_violations"] > packet.receiving_interface.announce_rate_grace:
rate_target = packet.receiving_interface.announce_rate_target
rate_penalty = packet.receiving_interface.announce_rate_penalty
rate_entry["blocked_until"] = rate_entry["last"] + rate_target + rate_penalty
rate_blocked = True
else:
rate_entry["last"] = now
else:
rate_blocked = True
*/
uint8_t retries = 0;
uint8_t announce_hops = packet.hops();
uint8_t local_rebroadcasts = 0;
bool block_rebroadcasts = false;
Interface attached_interface = {Type::NONE};
double retransmit_timeout = now + (Cryptography::random() * PATHFINDER_RW);
double expires;
if (packet.receiving_interface().mode() == Type::Interface::MODE_ACCESS_POINT) {
expires = now + AP_PATH_TIME;
}
else if (packet.receiving_interface().mode() == Type::Interface::MODE_ROAMING) {
expires = now + ROAMING_PATH_TIME;
}
else {
expires = now + PATHFINDER_E;
}
random_blobs.insert(random_blob);
if ((Reticulum::transport_enabled() || Transport::from_local_client(packet)) && packet.context() != Type::Packet::PATH_RESPONSE) {
// Insert announce into announce table for retransmission
if (rate_blocked) {
DEBUG("Blocking rebroadcast of announce from " + packet.destination_hash().toHex() + " due to excessive announce rate");
}
else {
if (Transport::from_local_client(packet)) {
// If the announce is from a local client,
// it is announced immediately, but only one time.
retransmit_timeout = now;
retries = PATHFINDER_R;
}
AnnounceEntry announce_entry(
now,
retransmit_timeout,
retries,
received_from,
announce_hops,
packet,
local_rebroadcasts,
block_rebroadcasts,
attached_interface
);
// CBA ACCUMULATES
_announce_table.insert({packet.destination_hash(), announce_entry});
}
}
// TODO: Check from_local_client once and store result
else if (Transport::from_local_client(packet) && packet.context() == Type::Packet::PATH_RESPONSE) {
// If this is a path response from a local client,
// check if any external interfaces have pending
// path requests.
//p if packet.destination_hash in Transport.pending_local_path_requests:
auto iter = _pending_local_path_requests.find(packet.destination_hash());
if (iter != _pending_local_path_requests.end()) {
//p desiring_interface = Transport.pending_local_path_requests.pop(packet.destination_hash)
//const Interface& desiring_interface = (*iter).second;
retransmit_timeout = now;
retries = PATHFINDER_R;
AnnounceEntry announce_entry(
now,
retransmit_timeout,
retries,
received_from,
announce_hops,
packet,
local_rebroadcasts,
block_rebroadcasts,
attached_interface
);
// CBA ACCUMULATES
_announce_table.insert({packet.destination_hash(), announce_entry});
}
}
// If we have any local clients connected, we re-
// transmit the announce to them immediately
if (_local_client_interfaces.size() > 0) {
Identity announce_identity(Identity::recall(packet.destination_hash()));
//Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown");
//announce_destination.hash(packet.destination_hash());
Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, packet.destination_hash());
//announce_destination.hexhash(announce_destination.hash().toHex());
Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
Bytes announce_data = packet.data();
// TODO: Shouldn't the context be PATH_RESPONSE in the first case here?
if (Transport::from_local_client(packet) && packet.context() == Type::Packet::PATH_RESPONSE) {
for (const Interface& local_interface : _local_client_interfaces) {
if (packet.receiving_interface() != local_interface) {
Packet new_announce(
announce_destination,
local_interface,
announce_data,
Type::Packet::ANNOUNCE,
announce_context,
Type::Transport::TRANSPORT,
Type::Packet::HEADER_2,
_identity.hash(),
true,
packet.context_flag()
);
new_announce.hops(packet.hops());
new_announce.send();
}
}
}
else {
for (const Interface& local_interface : _local_client_interfaces) {
if (packet.receiving_interface() != local_interface) {
Packet new_announce(
announce_destination,
local_interface,
announce_data,
Type::Packet::ANNOUNCE,
announce_context,
Type::Transport::TRANSPORT,
Type::Packet::HEADER_2,
_identity.hash(),
true,
packet.context_flag()
);
new_announce.hops(packet.hops());
new_announce.send();
}
}
}
}
// If we have any waiting discovery path requests
// for this destination, we retransmit to that
// interface immediately
auto iter = _discovery_path_requests.find(packet.destination_hash());
if (iter != _discovery_path_requests.end()) {
PathRequestEntry& pr_entry = (*iter).second;
attached_interface = pr_entry._requesting_interface;
DEBUG("Got matching announce, answering waiting discovery path request for " + packet.destination_hash().toHex() + " on " + attached_interface.toString());
Identity announce_identity(Identity::recall(packet.destination_hash()));
//Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown");
//announce_destination.hash(packet.destination_hash());
Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, packet.destination_hash());
//announce_destination.hexhash(announce_destination.hash().toHex());
Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
Bytes announce_data = packet.data();
Packet new_announce(
announce_destination,
attached_interface,
announce_data,
Type::Packet::ANNOUNCE,
Type::Packet::PATH_RESPONSE,
Type::Transport::TRANSPORT,
Type::Packet::HEADER_2,
_identity.hash(),
true,
packet.context_flag()
);
new_announce.hops(packet.hops());
new_announce.send();
}
// CBA Culling before adding to esnure table does not exceed maxsize
TRACE("Caching packet " + packet.get_hash().toHex());
if (RNS::Transport::cache_packet(packet, true)) {
packet.cached(true);
}
//TRACE("Adding packet " + packet.get_hash().toHex() + " to packet table");
//PacketEntry packet_entry(packet);
// CBA ACCUMULATES
//_packet_table.insert({packet.get_hash(), packet_entry});
TRACE("Adding destination " + packet.destination_hash().toHex() + " to path table");
DestinationEntry destination_table_entry(
now,
received_from,
announce_hops,
expires,
random_blobs,
//packet.receiving_interface(),
//const_cast<Interface&>(packet.receiving_interface()),
packet.receiving_interface().get_hash(),
//packet
packet.get_hash()
);
// CBA ACCUMULATES
if (_destination_table.insert({packet.destination_hash(), destination_table_entry}).second) {
++_destinations_added;
cull_path_table();
}
DEBUG("Destination " + packet.destination_hash().toHex() + " is now " + std::to_string(announce_hops) + " hops away via " + received_from.toHex() + " on " + packet.receiving_interface().toString());
// BOUNDARY MODE: Register destinations seen via non-backbone interfaces (Whitelist 1)
#ifdef BOUNDARY_MODE
{
bool is_backbone = is_backbone_interface(packet.receiving_interface());
if (!is_backbone) {
_boundary_local_addresses.insert(packet.destination_hash());
DEBUG("BOUNDARY: Registered local address " + packet.destination_hash().toHex() + " from local interface");
}
}
#endif
//TRACE("Transport::inbound: Destination " + packet.destination_hash().toHex() + " has data: " + packet.data().toHex());
//TRACE("Transport::inbound: Destination " + packet.destination_hash().toHex() + " has text: " + packet.data().toString());
// TODO
/*
// If the receiving interface is a tunnel, we add the
// announce to the tunnels table
if (packet.receiving_interface().tunnel_id()) {
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id];
paths = tunnel_entry[2];
paths[packet.destination_hash] = destination_table_entry;
expires = OS::time() + Transport::DESTINATION_TIMEOUT;
tunnel_entry[3] = expires;
DEBUG("Path to " + packet.destination_hash().toHex() + " associated with tunnel " + packet.receiving_interface().tunnel_id().toHex());
}
*/
// Call externally registered callbacks from apps
// wanting to know when an announce arrives
if (packet.context() != Type::Packet::PATH_RESPONSE) {
TRACE("Transport::inbound: Not path response, sending to announce handler...");
for (auto& handler : _announce_handlers) {
TRACE("Transport::inbound: Checking filter of announce handler...");
try {
// Check that the announced destination matches
// the handlers aspect filter
bool execute_callback = false;
Identity announce_identity(Identity::recall(packet.destination_hash()));
if (handler->aspect_filter().empty()) {
// If the handlers aspect filter is set to
// None, we execute the callback in all cases
execute_callback = true;
}
else {
Bytes handler_expected_hash = Destination::hash_from_name_and_identity(handler->aspect_filter().c_str(), announce_identity);
if (packet.destination_hash() == handler_expected_hash) {
execute_callback = true;
}
}
if (execute_callback) {
// CBA TODO Why does app data come from recall instead of from this announce packet?
handler->received_announce(
packet.destination_hash(),
announce_identity,
Identity::recall_app_data(packet.destination_hash())
);
}
}
catch (std::exception& e) {
ERROR("Error while processing external announce callback.");
ERRORF("The contained exception was: %s", e.what());
}
}
}
}
}
else {
TRACE("Transport::inbound: Packet is announce for local destination, not processing");
}
}
else {
TRACE("Transport::inbound: Packet is announce for local destination, not processing");
}
}
// Handling for link requests to local destinations
else if (packet.packet_type() == Type::Packet::LINKREQUEST) {
TRACE("Transport::inbound: Packet is LINKREQUEST");
if (!packet.transport_id() || packet.transport_id() == _identity.hash()) {
TRACE("Transport::inbound: Checking if LINKREQUEST is for local destination");
#if defined(DESTINATIONS_SET)
for (auto& destination : _destinations) {
if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(packet.destination_hash());
if (iter != _destinations.end()) {
auto& destination = (*iter).second;
if (destination.type() == packet.destination_type()) {
#endif
TRACE("Transport::inbound: Found local destination for LINKREQUEST");
packet.destination(destination);
// CBA iterator over std::set is always const so need to make temporarily mutable
//destination.receive(packet);
#if defined(DESTINATIONS_SET)
const_cast<Destination&>(destination).receive(packet);
#else
destination.receive(packet);
#endif
}
}
}
}
// Handling for data packets to local destinations
else if (packet.packet_type() == Type::Packet::DATA) {
TRACE("Transport::inbound: Packet is DATA");
if (packet.destination_type() == Type::Destination::LINK) {
// Data is destined for a link
TRACE("Transport::inbound: Packet is DATA for a LINK");
std::set<Link> active_links(_active_links);
for (auto& link : active_links) {
if (link.link_id() == packet.destination_hash()) {
TRACE("Transport::inbound: Packet is DATA for an active LINK");
packet.link(link);
const_cast<Link&>(link).receive(packet);
}
}
}
else {
// Data is basic (not destined for a link)
#if defined(DESTINATIONS_SET)
for (auto& destination : _destinations) {
if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(packet.destination_hash());
if (iter != _destinations.end()) {
// Data is for a local destination
DEBUG("Packet destination " + packet.destination_hash().toHex() + " found, destination is local");
auto& destination = (*iter).second;
if (destination.type() == packet.destination_type()) {
TRACE("Transport::inbound: Packet destination type " + std::to_string(packet.destination_type()) + " matched, processing");
#endif
packet.destination(destination);
#if defined(DESTINATIONS_SET)
const_cast<Destination&>(destination).receive(packet);
#else
destination.receive(packet);
#endif
if (destination.proof_strategy() == Type::Destination::PROVE_ALL) {
packet.prove();
}
else if (destination.proof_strategy() == Type::Destination::PROVE_APP) {
if (destination.callbacks()._proof_requested) {
try {
if (destination.callbacks()._proof_requested(packet)) {
packet.prove();
}
}
catch (std::exception& e) {
ERROR(std::string("Error while executing proof request callback. The contained exception was: ") + e.what());
}
}
}
}
else {
DEBUG("Transport::inbound: Packet destination type " + std::to_string(packet.destination_type()) + " mismatch, ignoring");
}
}
else {
DEBUG("Transport::inbound: Local destination " + packet.destination_hash().toHex() + " not found, not handling packet locally");
}
}
}
// Handling for proofs and link-request proofs
else if (packet.packet_type() == Type::Packet::PROOF) {
TRACE("Transport::inbound: Packet is PROOF");
if (packet.context() == Type::Packet::LRPROOF) {
TRACE("Transport::inbound: Packet is LINK PROOF");
// This is a link request proof, check if it
// needs to be transported
if ((Reticulum::transport_enabled() || for_local_client_link || from_local_client) && _link_table.find(packet.destination_hash()) != _link_table.end()) {
TRACE("Handling link request proof...");
LinkEntry link_entry = (*_link_table.find(packet.destination_hash())).second;
DEBUG("PROOF DEBUG: recv_iface=" + packet.receiving_interface().toString() + " out_iface=" + link_entry._outbound_interface.toString());
bool interface_match = (packet.receiving_interface() == link_entry._outbound_interface);
if (interface_match) {
try {
size_t expected_size = (Type::Identity::SIGLENGTH/8 + Type::Link::ECPUBSIZE/2);
size_t expected_size_with_mtu = expected_size + Type::Link::LINK_MTU_SIZE;
DEBUG("PROOF DEBUG: data_size=" + std::to_string(packet.data().size()) + " expected=" + std::to_string(expected_size) + " or " + std::to_string(expected_size_with_mtu) + " raw_size=" + std::to_string(packet.raw().size()));
if (packet.data().size() == expected_size || packet.data().size() == expected_size_with_mtu) {
Bytes signalling_bytes;
if (packet.data().size() == expected_size_with_mtu) {
signalling_bytes = Link::signalling_bytes(Link::mtu_from_lp_packet(packet), Link::mode_from_lp_packet(packet));
}
Bytes peer_pub_bytes = packet.data().mid(Type::Identity::SIGLENGTH/8, Type::Link::ECPUBSIZE/2);
Identity peer_identity = Identity::recall(link_entry._destination_hash);
if (!peer_identity) {
DEBUG("PROOF DEBUG: Cannot recall identity for " + link_entry._destination_hash.toHex() + ", dropping proof.");
}
else {
DEBUG("PROOF DEBUG: peer_identity recalled for " + link_entry._destination_hash.toHex() + " valid=" + std::to_string(!peer_identity.get_public_key().empty()));
Bytes peer_sig_pub_bytes = peer_identity.get_public_key().mid(Type::Link::ECPUBSIZE/2, Type::Link::ECPUBSIZE/2);
Bytes signed_data = packet.destination_hash() + peer_pub_bytes + peer_sig_pub_bytes + signalling_bytes;
Bytes signature = packet.data().left(Type::Identity::SIGLENGTH/8);
if (peer_identity.validate(signature, signed_data)) {
TRACE("Link request proof validated for transport via " + link_entry._receiving_interface.toString());
//p new_raw = packet.raw[0:1]
// CBA RESERVE
//Bytes new_raw = packet.raw().left(1);
Bytes new_raw(512);
new_raw << packet.raw().left(1);
//p new_raw += struct.pack("!B", packet.hops)
new_raw << packet.hops();
//p new_raw += packet.raw[2:]
new_raw << packet.raw().mid(2);
link_entry._validated = true;
transmit(link_entry._receiving_interface, new_raw);
}
else {
DEBUG("Invalid link request proof in transport for link " + packet.destination_hash().toHex() + ", dropping proof.");
}
} // end peer_identity valid
}
}
catch (std::exception& e) {
ERROR("Error while transporting link request proof. The contained exception was: " + std::string(e.what()));
}
}
else {
DEBUG("PROOF DEBUG: interface mismatch - recv=" + packet.receiving_interface().toString() + " expected_out=" + link_entry._outbound_interface.toString());
DEBUG("Link request proof received on wrong interface, not transporting it.");
}
}
else {
// Check if we can deliver it to a local
// pending link
TRACEF("Handling proof for link request %s", packet.destination_hash().toHex().c_str());
// CBA Must make a copy of _pending_links before traversing since it gets modified
//for (auto link : _pending_links) {
std::set<Link> pending_links(_pending_links);
for (auto& link : pending_links) {
TRACEF("Checking for link request handling by pending link %s", link.link_id().toHex().c_str());
if (link.link_id() == packet.destination_hash()) {
TRACE("Requesting pending link to validate proof");
const_cast<Link&>(link).validate_proof(packet);
}
}
}
}
else if (packet.context() == Type::Packet::RESOURCE_PRF) {
TRACE("Transport::inbound: Packet is RESOURCE PROOF");
std::set<Link> active_links(_active_links);
for (auto& link : active_links) {
if (link.link_id() == packet.destination_hash()) {
const_cast<Link&>(link).receive(packet);
}
}
}
else {
TRACE("Transport::inbound: Packet is regular PROOF");
if (packet.destination_type() == Type::Destination::LINK) {
std::set<Link> active_links(_active_links);
for (auto& link : active_links) {
if (link.link_id() == packet.destination_hash()) {
packet.link(link);
}
}
}
Bytes proof_hash;
if (packet.data().size() == Type::PacketReceipt::EXPL_LENGTH) {
proof_hash = packet.data().left(Type::Identity::HASHLENGTH/8);
}
// Check if this proof needs to be transported
if ((Reticulum::transport_enabled() || from_local_client || proof_for_local_client) && _reverse_table.find(packet.destination_hash()) != _reverse_table.end()) {
ReverseEntry reverse_entry = (*_reverse_table.find(packet.destination_hash())).second;
if (packet.receiving_interface() == reverse_entry._outbound_interface) {
TRACE("Proof received on correct interface, transporting it via " + reverse_entry._receiving_interface.toString());
//p new_raw = packet.raw[0:1]
// CBA RESERVE
//Bytes new_raw = packet.raw().left(1);
Bytes new_raw(512);
new_raw << packet.raw().left(1);
//p new_raw += struct.pack("!B", packet.hops)
new_raw << packet.hops();
//p new_raw += packet.raw[2:]
new_raw << packet.raw().mid(2);
transmit(reverse_entry._receiving_interface, new_raw);
}
else {
DEBUG("Proof received on wrong interface, not transporting it.");
}
}
else {
TRACE("Proof is not candidate for transporting");
}
std::list<PacketReceipt> cull_receipts;
for (auto& receipt : _receipts) {
bool receipt_validated = false;
if (proof_hash) {
// Only test validation if hash matches
if (receipt.hash() == proof_hash) {
receipt_validated = receipt.validate_proof_packet(packet);
}
}
else {
// TODO: This looks like it should actually
// be rewritten when implicit proofs are added.
// In case of an implicit proof, we have
// to check every single outstanding receipt
receipt_validated = receipt.validate_proof_packet(packet);
}
// CBA TODO requires modifying of collection while iterating which is forbidden
if (receipt_validated) {
//p if receipt in Transport.receipts:
//p Transport.receipts.remove(receipt)
cull_receipts.push_back(receipt);
}
}
// CBA since modifying of collection while iterating is forbidden
for (auto& receipt : _receipts) {
cull_receipts.remove(receipt);
}
}
}
}
_jobs_locked = false;
}
/*static*/ void Transport::synthesize_tunnel(const Interface& interface) {
// TODO
/*p
Bytes interface_hash = interface.get_hash();
Bytes public_key = _identity.get_public_key();
Bytes random_hash = Identity::get_random_hash();
tunnel_id_data = public_key+interface_hash
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
signed_data = tunnel_id_data+random_hash
signature = Transport.identity.sign(signed_data)
data = signed_data+signature
tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface)
packet.send()
interface.wants_tunnel = False
*/
}
/*static*/ void Transport::tunnel_synthesize_handler(const Bytes& data, const Packet& packet) {
// TODO
/*p
try:
expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
if len(data) == expected_length:
public_key = data[:RNS.Identity.KEYSIZE//8]
interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8]
tunnel_id_data = public_key+interface_hash
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length]
signed_data = tunnel_id_data+random_hash
remote_transport_identity = RNS.Identity(create_keys=False)
remote_transport_identity.load_public_key(public_key)
if remote_transport_identity.validate(signature, signed_data):
Transport.handle_tunnel(tunnel_id, packet.receiving_interface)
except Exception as e:
RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
*/
}
/*static*/ void Transport::handle_tunnel(const Bytes& tunnel_id, const Interface& interface) {
// TODO
/*p
expires = time.time() + Transport.DESTINATION_TIMEOUT
if not tunnel_id in Transport.tunnels:
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG)
paths = {}
tunnel_entry = [tunnel_id, interface, paths, expires]
interface.tunnel_id = tunnel_id
Transport.tunnels[tunnel_id] = tunnel_entry
else:
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG)
tunnel_entry = Transport.tunnels[tunnel_id]
tunnel_entry[1] = interface
tunnel_entry[3] = expires
interface.tunnel_id = tunnel_id
paths = tunnel_entry[2]
deprecated_paths = []
for destination_hash, path_entry in paths.items():
received_from = path_entry[1]
announce_hops = path_entry[2]
expires = path_entry[3]
random_blobs = path_entry[4]
receiving_interface = interface
packet = path_entry[6]
new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet]
should_add = False
if destination_hash in Transport.destination_table:
old_entry = Transport.destination_table[destination_hash]
old_hops = old_entry[2]
old_expires = old_entry[3]
if announce_hops <= old_hops or time.time() > old_expires:
should_add = True
else:
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
else:
if time.time() < expires:
should_add = True
else:
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because it has expired", RNS.LOG_DEBUG)
if should_add:
Transport.destination_table[destination_hash] = new_entry
RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG)
else:
deprecated_paths.append(destination_hash)
for deprecated_path in deprecated_paths:
RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG)
paths.pop(deprecated_path)
*/
}
/*static*/ void Transport::register_interface(Interface& interface) {
TRACE("Transport: Registering interface " + interface.get_hash().toHex() + " " + interface.toString());
#if defined(INTERFACES_SET)
_interfaces.insert(interface);
#elif defined(INTERFACES_LIST)
_interfaces.push_back(interface);
#elif defined(INTERFACES_MAP)
_interfaces.insert({interface.get_hash(), interface});
#endif
// CBA TODO set or add transport as listener on interface to receive incoming packets?
}
/*static*/ void Transport::deregister_interface(const Interface& interface) {
TRACE("Transport: Deregistering interface " + interface.toString());
#if defined(INTERFACES_SET)
//for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) {
// if ((*iter).get() == interface) {
// _interfaces.erase(iter);
// TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString());
// break;
// }
//}
//auto iter = _interfaces.find(interface);
auto iter = _interfaces.find(const_cast<Interface&>(interface));
if (iter != _interfaces.end()) {
_interfaces.erase(iter);
TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString());
}
#elif defined(INTERFACES_LIST)
for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) {
if ((*iter).get() == interface) {
_interfaces.erase(iter);
TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString());
break;
}
}
#elif defined(INTERFACES_MAP)
auto iter = _interfaces.find(interface.get_hash());
if (iter != _interfaces.end()) {
TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).second.toString());
_interfaces.erase(iter);
}
#endif
}
/*static*/ void Transport::register_destination(Destination& destination) {
//TRACE("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
TRACE("Transport: Registering destination " + destination.toString());
destination.mtu(Type::Reticulum::MTU);
if (destination.direction() == Type::Destination::IN) {
#if defined(DESTINATIONS_SET)
for (auto& registered_destination : _destinations) {
if (destination.hash() == registered_destination.hash()) {
//p raise KeyError("Attempt to register an already registered destination.")
throw std::runtime_error("Attempt to register an already registered destination.");
}
}
// CBA ACCUMULATES
_destinations.insert(destination);
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(destination.hash());
if (iter != _destinations.end()) {
//p raise KeyError("Attempt to register an already registered destination.")
throw std::runtime_error("Attempt to register an already registered destination.");
}
// CBA ACCUMULATES
_destinations.insert({destination.hash(), destination});
#endif
if (_owner && _owner.is_connected_to_shared_instance()) {
if (destination.type() == Type::Destination::SINGLE) {
TRACE("Transport:register_destination: Announcing destination " + destination.toString());
destination.announce({}, true);
}
}
}
else {
TRACE("Transport:register_destination: Skipping registration (not direction IN) of destination " + destination.toString());
}
/*
#if defined(DESTINATIONS_SET)
for (const Destination& destination : _destinations) {
#elif defined(DESTINATIONS_MAP)
for (auto& [hash, destination] : _destinations) {
#endif
TRACE("Transport::register_destination: Listed destination " + destination.toString());
}
*/
//TRACE("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
/*static*/ void Transport::deregister_destination(const Destination& destination) {
TRACE("Transport: Deregistering destination " + destination.toString());
#if defined(DESTINATIONS_SET)
if (_destinations.find(destination) != _destinations.end()) {
_destinations.erase(destination);
TRACE("Transport::deregister_destination: Found and removed destination " + destination.toString());
}
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(destination.hash());
if (iter != _destinations.end()) {
_destinations.erase(iter);
TRACE("Transport::deregister_destination: Found and removed destination " + (*iter).second.toString());
}
#endif
}
/*static*/ void Transport::register_link(Link& link) {
TRACE("Transport: Registering link " + link.toString());
if (link.initiator()) {
// CBA ACCUMULATES
_pending_links.insert(link);
}
else {
// CBA ACCUMULATES
_active_links.insert(link);
}
}
/*static*/ void Transport::activate_link(Link& link) {
TRACE("Transport: Activating link " + link.toString());
if (_pending_links.find(link) != _pending_links.end()) {
if (link.status() != Type::Link::ACTIVE) {
throw std::runtime_error("Invalid link state for link activation: " + std::to_string(link.status()));
}
_pending_links.erase(link);
// CBA ACCUMULATES
_active_links.insert(link);
link.status(Type::Link::ACTIVE);
}
else {
ERROR("Attempted to activate a link that was not in the pending table");
}
}
/*
Registers an announce handler.
:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example<example-announce>` for more info.
*/
/*static*/ void Transport::register_announce_handler(HAnnounceHandler handler) {
TRACE("Transport: Registering announce handler " + handler->aspect_filter());
_announce_handlers.insert(handler);
}
/*
Deregisters an announce handler.
:param handler: The announce handler to be deregistered.
*/
/*static*/ void Transport::deregister_announce_handler(HAnnounceHandler handler) {
TRACE("Transport: Deregistering announce handler " + handler->aspect_filter());
if (_announce_handlers.find(handler) != _announce_handlers.end()) {
_announce_handlers.erase(handler);
TRACE("Transport::deregister_announce_handler: Found and removed handler" + handler->aspect_filter());
}
}
/*static*/ Interface Transport::find_interface_from_hash(const Bytes& interface_hash) {
#if defined(INTERFACES_SET)
for (const Interface& interface : _interfaces) {
if (interface.get_hash() == interface_hash) {
TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString());
return interface;
}
}
#elif defined(INTERFACES_LIST)
for (Interface& interface : _interfaces) {
if (interface.get_hash() == interface_hash) {
TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString());
return interface;
}
}
#elif defined(INTERFACES_MAP)
auto iter = _interfaces.find(interface_hash);
if (iter != _interfaces.end()) {
TRACE("Transport::find_interface_from_hash: Found interface " + (*iter).second.toString());
return (*iter).second;
}
#endif
return {Type::NONE};
}
/*static*/ bool Transport::should_cache_packet(const Packet& packet) {
// TODO: Rework the caching system. It's currently
// not very useful to even cache Resource proofs,
// disabling it for now, until redesigned.
// if packet.context == RNS.Packet.RESOURCE_PRF:
// return True
return false;
}
// When caching packets to storage, they are written
// exactly as they arrived over their interface. This
// means that they have not had their hop count
// increased yet! Take note of this when reading from
// the packet cache.
/*static*/ bool Transport::cache_packet(const Packet& packet, bool force_cache /*= false*/) {
TRACE("Checking to see if packet " + packet.get_hash().toHex() + " should be cached");
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
if (should_cache_packet(packet) || force_cache) {
TRACE("Saving packet " + packet.get_hash().toHex() + " to storage");
try {
char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet.get_hash().toHex().c_str());
return (Persistence::serialize(packet, packet_cache_path) > 0);
}
catch (std::exception& e) {
ERROR("Error writing packet to cache. The contained exception was: " + std::string(e.what()));
}
}
#endif
return false;
}
/*static*/ Packet Transport::get_cached_packet(const Bytes& packet_hash) {
TRACE("Loading packet " + packet_hash.toHex() + " from cache storage");
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
try {
/*p
packet_hash = RNS.hexrep(packet_hash, delimit=False)
path = RNS.Reticulum.cachepath+"/"+packet_hash
if os.path.isfile(path):
file = open(path, "rb")
cached_data = umsgpack.unpackb(file.read())
file.close()
packet = RNS.Packet(None, cached_data[0])
interface_reference = cached_data[1]
for interface in Transport.interfaces:
if str(interface) == interface_reference:
packet.receiving_interface = interface
return packet
else:
return None
*/
char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet_hash.toHex().c_str());
Packet packet({Type::NONE});
if (Persistence::deserialize(packet, packet_cache_path) > 0) {
packet.unpack();
}
return packet;
}
catch (std::exception& e) {
ERROR("Exception occurred while getting cached packet.");
ERRORF("The contained exception was: %s", e.what());
}
#endif
return {Type::NONE};
}
/*static*/ bool Transport::clear_cached_packet(const Bytes& packet_hash) {
TRACE("Clearing packet " + packet_hash.toHex() + " from cache storage");
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
try {
char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet_hash.toHex().c_str());
double start_time = OS::time();
bool success = RNS::Utilities::OS::remove_file(packet_cache_path);
double diff_time = OS::time() - start_time;
if (diff_time < 1.0) {
DEBUG("Remove cached packet in " + std::to_string((int)(diff_time*1000)) + " ms");
}
else {
DEBUG("Remove cached packet in " + std::to_string(diff_time) + " s");
}
}
catch (std::exception& e) {
ERROR("Exception occurred while clearing cached packet.");
ERRORF("The contained exception was: %s", e.what());
}
#endif
return false;
}
/*static*/ bool Transport::cache_request_packet(const Packet& packet) {
if (packet.data().size() == Type::Identity::HASHLENGTH/8) {
const Packet& cached_packet = get_cached_packet(packet.data());
if (cached_packet) {
// If the packet was retrieved from the local
// cache, replay it to the Transport instance,
// so that it can be directed towards it original
// destination.
inbound(cached_packet.raw(), cached_packet.receiving_interface());
return true;
}
else {
return false;
}
}
else {
return false;
}
}
/*static*/ void Transport::cache_request(const Bytes& packet_hash, const Destination& destination) {
const Packet& cached_packet = get_cached_packet(packet_hash);
if (cached_packet) {
// The packet was found in the local cache,
// replay it to the Transport instance.
inbound(cached_packet.raw(), cached_packet.receiving_interface());
}
else {
// The packet is not in the local cache,
// query the network.
Packet request(destination, packet_hash, Type::Packet::DATA, Type::Packet::CACHE_REQUEST);
request.send();
}
}
/*static*/ bool Transport::remove_path(const Bytes& destination_hash) {
if (_destination_table.erase(destination_hash) > 0) {
// CBA also remove cached announce packet if exists
}
return false;
}
/*
:param destination_hash: A destination hash as *bytes*.
:returns: *True* if a path to the destination is known, otherwise *False*.
*/
/*static*/ bool Transport::has_path(const Bytes& destination_hash) {
if (_destination_table.find(destination_hash) != _destination_table.end()) {
return true;
}
else {
return false;
}
}
/*
:param destination_hash: A destination hash as *bytes*.
:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown.
*/
/*static*/ uint8_t Transport::hops_to(const Bytes& destination_hash) {
auto iter = _destination_table.find(destination_hash);
if (iter != _destination_table.end()) {
DestinationEntry destination_entry = (*iter).second;
return destination_entry._hops;
}
else {
return PATHFINDER_M;
}
}
/*
:param destination_hash: A destination hash as *bytes*.
:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown.
*/
/*static*/ Bytes Transport::next_hop(const Bytes& destination_hash) {
auto iter = _destination_table.find(destination_hash);
if (iter != _destination_table.end()) {
DestinationEntry destination_entry = (*iter).second;
return destination_entry._received_from;
}
else {
return {};
}
}
/*
:param destination_hash: A destination hash as *bytes*.
:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown.
*/
/*static*/ Interface Transport::next_hop_interface(const Bytes& destination_hash) {
auto iter = _destination_table.find(destination_hash);
if (iter != _destination_table.end()) {
DestinationEntry destination_entry = (*iter).second;
return destination_entry.receiving_interface();
}
else {
return {Type::NONE};
}
}
/*static*/ uint32_t Transport::next_hop_interface_bitrate(const Bytes& destination_hash) {
const Interface& interface = next_hop_interface(destination_hash);
if (interface) {
return interface.bitrate();
}
else {
return 0;
}
}
/*static*/ uint16_t Transport::next_hop_interface_hw_mtu(const Bytes& destination_hash) {
const Interface& interface = next_hop_interface(destination_hash);
if (interface) {
if (interface.AUTOCONFIGURE_MTU() || interface.FIXED_MTU()) return interface.HW_MTU();
else return 0;
}
else {
return 0;
}
}
/*static*/ double Transport::next_hop_per_bit_latency(const Bytes& destination_hash) {
uint32_t bitrate = next_hop_interface_bitrate(destination_hash);
if (bitrate > 0) {
return (1.0/(double)bitrate);
}
else {
return 0.0;
}
}
/*static*/ double Transport::next_hop_per_byte_latency(const Bytes& destination_hash) {
double per_bit_latency = next_hop_per_bit_latency(destination_hash);
if (per_bit_latency > 0.0) {
return per_bit_latency*8.0;
}
else {
return 0.0;
}
}
/*static*/ double Transport::first_hop_timeout(const Bytes& destination_hash) {
double latency = next_hop_per_byte_latency(destination_hash);
if (latency > 0.0) {
return RNS::Type::Reticulum::MTU * latency + RNS::Type::Reticulum::DEFAULT_PER_HOP_TIMEOUT;
}
else {
return RNS::Type::Reticulum::DEFAULT_PER_HOP_TIMEOUT;
}
}
/*static*/ double Transport::extra_link_proof_timeout(const Interface& interface) {
if (interface) {
return ((1.0/(double)interface.bitrate())*8.0)*RNS::Type::Reticulum::MTU;
}
else {
return 0.0;
}
}
/*static*/ bool Transport::expire_path(const Bytes& destination_hash) {
auto iter = _destination_table.find(destination_hash);
if (iter != _destination_table.end()) {
DestinationEntry destination_entry = (*iter).second;
destination_entry._timestamp = 0;
_tables_last_culled = 0;
return true;
}
else {
return false;
}
}
/*p
@staticmethod
def mark_path_unresponsive(destination_hash):
if destination_hash in Transport.destination_table:
Transport.path_states[destination_hash] = Transport.STATE_UNRESPONSIVE
return True
else:
return False
@staticmethod
def mark_path_responsive(destination_hash):
if destination_hash in Transport.destination_table:
Transport.path_states[destination_hash] = Transport.STATE_RESPONSIVE
return True
else:
return False
@staticmethod
def mark_path_unknown_state(destination_hash):
if destination_hash in Transport.destination_table:
Transport.path_states[destination_hash] = Transport.STATE_UNKNOWN
return True
else:
return False
@staticmethod
def path_is_unresponsive(destination_hash):
if destination_hash in Transport.path_states:
if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE:
return True
return False
*/
/*
Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
will announce it.
:param destination_hash: A destination hash as *bytes*.
:param on_interface: If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used.
*/
///*static*/ void Transport::request_path(const Bytes& destination_hash, const Interface& on_interface /*= {Type::NONE}*/, const Bytes& tag /*= {}*/, bool recursive /*= false*/) {
/*static*/ void Transport::request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag /*= {}*/, bool recursive /*= false*/) {
Bytes request_tag;
if (!tag) {
request_tag = Identity::get_random_hash();
}
else {
request_tag = tag;
}
Bytes path_request_data;
if (Reticulum::transport_enabled()) {
path_request_data = destination_hash + _identity.hash() + request_tag;
}
else {
path_request_data = destination_hash + request_tag;
}
Destination path_request_dst({Type::NONE}, Type::Destination::OUT, Type::Destination::PLAIN, Type::Transport::APP_NAME, "path.request");
Packet packet(path_request_dst, on_interface, path_request_data, Type::Packet::DATA, Type::Packet::CONTEXT_NONE, Type::Transport::BROADCAST, Type::Packet::HEADER_1);
if (on_interface && recursive) {
// TODO
/*p
if not hasattr(on_interface, "announce_cap"):
on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
if not hasattr(on_interface, "announce_allowed_at"):
on_interface.announce_allowed_at = 0
if not hasattr(on_interface, "announce_queue"):
on_interface.announce_queue = []
*/
bool queued_announces = (on_interface.announce_queue().size() > 0);
if (queued_announces) {
TRACE("Blocking recursive path request on " + on_interface.toString() + " due to queued announces");
return;
}
else {
double now = OS::time();
if (now < on_interface.announce_allowed_at()) {
TRACE("Blocking recursive path request on " + on_interface.toString() + " due to active announce cap");
return;
}
else {
//p tx_time = ((len(path_request_data)+RNS.Reticulum.HEADER_MINSIZE)*8) / on_interface.bitrate
uint32_t wait_time = 0;
if ( on_interface.bitrate() > 0 && on_interface.announce_cap() > 0) {
uint32_t tx_time = ((path_request_data.size() + Type::Reticulum::HEADER_MINSIZE)*8) / on_interface.bitrate();
wait_time = (tx_time / on_interface.announce_cap());
}
const_cast<Interface&>(on_interface).announce_allowed_at(now + wait_time);
}
}
}
packet.send();
_path_requests[destination_hash] = OS::time();
}
/*static*/ void Transport::request_path(const Bytes& destination_hash) {
return request_path(destination_hash, {Type::NONE});
}
/*static*/ void Transport::path_request_handler(const Bytes& data, const Packet& packet) {
TRACE("Transport::path_request_handler");
try {
// If there is at least bytes enough for a destination
// hash in the packet, we assume those bytes are the
// destination being requested.
if (data.size() >= Type::Identity::TRUNCATED_HASHLENGTH/8) {
Bytes destination_hash = data.left(Type::Identity::TRUNCATED_HASHLENGTH/8);
//TRACE("Transport::path_request_handler: destination_hash: " + destination_hash.toHex());
// If there is also enough bytes for a transport
// instance ID and at least one tag byte, we
// assume the next bytes to be the trasport ID
// of the requesting transport instance.
Bytes requesting_transport_instance;
if (data.size() > (Type::Identity::TRUNCATED_HASHLENGTH/8)*2) {
requesting_transport_instance = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8, Type::Identity::TRUNCATED_HASHLENGTH/8);
//TRACE("Transport::path_request_handler: requesting_transport_instance: " + requesting_transport_instance.toHex());
}
Bytes tag_bytes;
if (data.size() > Type::Identity::TRUNCATED_HASHLENGTH/8*2) {
tag_bytes = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8*2);
}
else if (data.size() > Type::Identity::TRUNCATED_HASHLENGTH/8) {
tag_bytes = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8);
}
if (tag_bytes) {
//TRACE("Transport::path_request_handler: tag_bytes: " + tag_bytes.toHex());
if (tag_bytes.size() > Type::Identity::TRUNCATED_HASHLENGTH/8) {
tag_bytes = tag_bytes.left(Type::Identity::TRUNCATED_HASHLENGTH/8);
}
Bytes unique_tag = destination_hash + tag_bytes;
//TRACE("Transport::path_request_handler: unique_tag: " + unique_tag.toHex());
if (_discovery_pr_tags.find(unique_tag) == _discovery_pr_tags.end()) {
// CBA ACCUMULATES
_discovery_pr_tags.insert(unique_tag);
path_request(
destination_hash,
from_local_client(packet),
packet.receiving_interface(),
requesting_transport_instance,
tag_bytes
);
}
else {
DEBUG("Ignoring duplicate path request for " + destination_hash.toHex() + " with tag " + unique_tag.toHex());
}
}
else {
DEBUG("Ignoring tagless path request for " + destination_hash.toHex());
}
}
}
catch (std::exception& e) {
ERROR("Error while handling path request. The contained exception was: " + std::string(e.what()));
}
}
/*static*/ void Transport::path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id /*= {}*/, const Bytes& tag /*= {}*/) {
TRACE("Transport::path_request");
bool should_search_for_unknown = false;
std::string interface_str;
if (attached_interface) {
if (Reticulum::transport_enabled() && (attached_interface.mode() & Interface::DISCOVER_PATHS_FOR) > 0) {
TRACE("Transport::path_request_handler: interface allows searching for unknown paths");
should_search_for_unknown = true;
}
interface_str = " on " + attached_interface.toString();
}
DEBUG("Path request for destination " + destination_hash.toHex() + interface_str);
bool destination_exists_on_local_client = false;
if (_local_client_interfaces.size() > 0) {
auto iter = _destination_table.find(destination_hash);
if (iter != _destination_table.end()) {
TRACE("Transport::path_request_handler: entry found for destination " + destination_hash.toHex());
DestinationEntry& destination_entry = (*iter).second;
if (is_local_client_interface(destination_entry.receiving_interface())) {
destination_exists_on_local_client = true;
// CBA ACCUMULATES
_pending_local_path_requests.insert({destination_hash, attached_interface});
}
}
else {
TRACE("Transport::path_request_handler: entry not found for destination " + destination_hash.toHex());
}
}
auto destination_iter = _destination_table.find(destination_hash);
//local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
#if defined(DESTINATIONS_SET)
Destination local_destination({Type::NONE});
for (auto& destination : _destinations) {
if (destination.hash() == destination_hash) {
local_destination = destination;
break;
}
}
//if local_destination != None:
if (local_destination) {
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(destination_hash);
if (iter != _destinations.end()) {
auto& local_destination = (*iter).second;
#endif
local_destination.announce({Bytes::NONE}, true, attached_interface, tag);
DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", destination is local to this system");
}
//p elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table):
else if ((Reticulum::transport_enabled() || is_from_local_client) && destination_iter != _destination_table.end()) {
TRACE("Transport::path_request_handler: entry found for destination " + destination_hash.toHex());
DestinationEntry& destination_entry = (*destination_iter).second;
const Packet& announce_packet = destination_entry.announce_packet();
const Bytes& next_hop = destination_entry._received_from;
const Interface& receiving_interface = destination_entry.receiving_interface();
if (attached_interface.mode() == Type::Interface::MODE_ROAMING && attached_interface == receiving_interface) {
DEBUG("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface");
}
else {
if (requestor_transport_id && destination_entry._received_from == requestor_transport_id) {
// TODO: Find a bandwidth efficient way to invalidate our
// known path on this signal. The obvious way of signing
// path requests with transport instance keys is quite
// inefficient. There is probably a better way. Doing
// path invalidation here would decrease the network
// convergence time. Maybe just drop it?
DEBUG("Not answering path request for destination " + destination_hash.toHex() + interface_str + ", since next hop is the requestor");
}
else {
DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", path is known");
double now = OS::time();
uint8_t retries = Type::Transport::PATHFINDER_R;
uint8_t local_rebroadcasts = 0;
bool block_rebroadcasts = true;
// CBA TODO Determine if okay to take hops directly from DestinationEntry
uint8_t announce_hops = announce_packet.hops();
double retransmit_timeout = 0;
if (is_from_local_client) {
retransmit_timeout = now;
}
else {
// TODO: Look at this timing
retransmit_timeout = now + Type::Transport::PATH_REQUEST_GRACE /*+ (RNS.rand() * Transport.PATHFINDER_RW)*/;
}
// This handles an edge case where a peer sends a past
// request for a destination just after an announce for
// said destination has arrived, but before it has been
// rebroadcast locally. In such a case the actual announce
// is temporarily held, and then reinserted when the path
// request has been served to the peer.
auto announce_iter = _announce_table.find(announce_packet.destination_hash());
if (announce_iter != _announce_table.end()) {
AnnounceEntry& held_entry = (*announce_iter).second;
// CBA ACCUMULATES
_held_announces.insert({announce_packet.destination_hash(), held_entry});
}
/*
// CBA ACCUMULATES
_announce_table.insert({announce_packet.destination_hash(), {
now,
retransmit_timeout,
retries,
// BUG?
//destination_entry.receiving_interface,
destination_entry._received_from,
announce_hops,
announce_packet,
local_rebroadcasts,
block_rebroadcasts,
attached_interface
}});
*/
AnnounceEntry announce_entry(
now,
retransmit_timeout,
retries,
// BUG?
//destination_entry.receiving_interface,
destination_entry._received_from,
announce_hops,
announce_packet,
local_rebroadcasts,
block_rebroadcasts,
attached_interface
);
// CBA ACCUMULATES
_announce_table.insert({announce_packet.destination_hash(), announce_entry});
}
}
}
else if (is_from_local_client) {
// Forward path request on all interfaces
// except the local client
DEBUG("Forwarding path request from local client for destination " + destination_hash.toHex() + interface_str + " to all other interfaces");
Bytes request_tag = Identity::get_random_hash();
#if defined(INTERFACES_SET)
for (const Interface& interface : _interfaces) {
#elif defined(INTERFACES_LIST)
for (Interface& interface : _interfaces) {
#elif defined(INTERFACES_MAP)
for (auto& [hash, interface] : _interfaces) {
#endif
if (interface != attached_interface) {
request_path(destination_hash, interface, request_tag);
}
}
}
else if (should_search_for_unknown) {
TRACE("Transport::path_request_handler: searching for unknown path to " + destination_hash.toHex());
if (_discovery_path_requests.find(destination_hash) != _discovery_path_requests.end()) {
DEBUG("There is already a waiting path request for destination " + destination_hash.toHex() + " on behalf of path request" + interface_str);
}
else {
// Forward path request on all interfaces
// except the requestor interface
DEBUG("Attempting to discover unknown path to destination " + destination_hash.toHex() + " on behalf of path request" + interface_str);
//p pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface }
//p _discovery_path_requests[destination_hash] = pr_entry;
// CBA ACCUMULATES
_discovery_path_requests.insert({destination_hash, {
destination_hash,
OS::time() + Type::Transport::PATH_REQUEST_TIMEOUT,
attached_interface
}});
#if defined(BOUNDARY_MODE)
// BOUNDARY: Track this destination in Whitelist 2 so the path
// response announce from the backbone will be allowed through
_boundary_mentioned_addresses.insert(destination_hash);
#endif
#if defined(INTERFACES_SET)
for (const Interface& interface : _interfaces) {
#elif defined(INTERFACES_LIST)
for (Interface& interface : _interfaces) {
#elif defined(INTERFACES_MAP)
for (auto& [hash, interface] : _interfaces) {
#endif
// CBA EXPERIMENTAL forwarding path requests even on requestor interface in order to support
// path-finding over LoRa mesh
//if (interface != attached_interface) {
if (true) {
TRACE("Transport::path_request: requesting path on interface " + interface.toString());
// Use the previously extracted tag from this path request
// on the new path requests as well, to avoid potential loops
request_path(destination_hash, interface, tag, true);
}
else {
TRACE("Transport::path_request: not requesting path on same interface " + interface.toString());
}
}
}
}
else if (!is_from_local_client && _local_client_interfaces.size() > 0) {
// Forward the path request on all local
// client interfaces
DEBUG("Forwarding path request for destination " + destination_hash.toHex() + interface_str + " to local clients");
for (const Interface& interface : _local_client_interfaces) {
request_path(destination_hash, interface);
}
}
else {
DEBUG("Ignoring path request for destination " + destination_hash.toHex() + interface_str + ", no path known");
}
}
/*static*/ bool Transport::from_local_client(const Packet& packet) {
if (packet.receiving_interface().parent_interface()) {
return is_local_client_interface(packet.receiving_interface());
}
else {
return false;
}
}
/*static*/ bool Transport::is_local_client_interface(const Interface& interface) {
if (interface.parent_interface()) {
if (interface.parent_interface()->is_local_shared_instance()) {
return true;
}
else {
return false;
}
}
else {
return false;
}
}
/*static*/ bool Transport::interface_to_shared_instance(const Interface& interface) {
if (interface.is_connected_to_shared_instance()) {
return true;
}
else {
return false;
}
}
/*static*/ void Transport::detach_interfaces() {
// TODO
/*p
detachable_interfaces = []
for interface in Transport.interfaces:
// Currently no rules are being applied
// here, and all interfaces will be sent
// the detach call on RNS teardown.
if True:
detachable_interfaces.append(interface)
else:
pass
for interface in Transport.local_client_interfaces:
// Currently no rules are being applied
// here, and all interfaces will be sent
// the detach call on RNS teardown.
if True:
detachable_interfaces.append(interface)
else:
pass
for interface in detachable_interfaces:
interface.detach()
*/
}
/*static*/ void Transport::shared_connection_disappeared() {
// TODO
/*p
for link in Transport.active_links:
link.teardown()
for link in Transport.pending_links:
link.teardown()
Transport.announce_table = {}
Transport.destination_table = {}
Transport.reverse_table = {}
Transport.link_table = {}
Transport.held_announces = {}
Transport.announce_handlers = []
Transport.tunnels = {}
*/
}
/*static*/ void Transport::shared_connection_reappeared() {
// TODO
/*p
if Transport.owner.is_connected_to_shared_instance:
for registered_destination in Transport.destinations:
if registered_destination.type == RNS.Destination.SINGLE:
registered_destination.announce(path_response=True)
*/
}
/*static*/ void Transport::drop_announce_queues() {
// TODO
/*p
for interface in Transport.interfaces:
if hasattr(interface, "announce_queue") and interface.announce_queue != None:
na = len(interface.announce_queue)
if na > 0:
if na == 1:
na_str = "1 announce"
else:
na_str = str(na)+" announces"
interface.announce_queue = []
RNS.log("Dropped "+na_str+" on "+str(interface), RNS.LOG_VERBOSE)
*/
}
/*static*/ uint64_t Transport::announce_emitted(const Packet& packet) {
//p random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
//p announce_emitted = int.from_bytes(random_blob[5:10], "big")
Bytes random_blob = packet.data().mid(RNS::Type::Identity::KEYSIZE/8+RNS::Type::Identity::NAME_HASH_LENGTH/8, 10);
if (random_blob) {
return OS::from_bytes_big_endian(random_blob.data() + 5, 5);
}
return 0;
}
/*static*/ void Transport::write_packet_hashlist() {
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
// TODO
/*p
if not Transport.owner.is_connected_to_shared_instance:
if hasattr(Transport, "saving_packet_hashlist"):
wait_interval = 0.2
wait_timeout = 5
wait_start = time.time()
while Transport.saving_packet_hashlist:
time.sleep(wait_interval)
if time.time() > wait_start+wait_timeout:
RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
return False
try:
Transport.saving_packet_hashlist = True
save_start = time.time()
if not RNS.Reticulum.transport_enabled():
Transport.packet_hashlist = []
else:
RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
file = open(packet_hashlist_path, "wb")
file.write(umsgpack.packb(Transport.packet_hashlist))
file.close()
save_time = time.time() - save_start
if save_time < 1:
time_str = str(round(save_time*1000,2))+"ms"
else:
time_str = str(round(save_time,2))+"s"
RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
Transport.saving_packet_hashlist = False
*/
#endif
}
//#define CUSTOM 1
/*static*/ bool Transport::read_path_table() {
DEBUG("Transport::read_path_table");
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
if (!_owner.is_connected_to_shared_instance() && OS::file_exists(destination_table_path)) {
/*p
serialised_destinations = []
try:
file = open(destination_table_path, "rb")
serialised_destinations = umsgpack.unpackb(file.read())
file.close()
for serialised_entry in serialised_destinations:
destination_hash = serialised_entry[0]
if len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
timestamp = serialised_entry[1]
received_from = serialised_entry[2]
hops = serialised_entry[3]
expires = serialised_entry[4]
random_blobs = serialised_entry[5]
receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
announce_packet = Transport.get_cached_packet(serialised_entry[7])
if announce_packet != None and receiving_interface != None:
announce_packet.unpack()
// We increase the hops, since reading a packet
// from cache is equivalent to receiving it again
// over an interface. It is cached with it's non-
// increased hop-count.
announce_packet.hops += 1
Transport.destination_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG)
else:
RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
if announce_packet == None:
RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG)
if receiving_interface == None:
RNS.log("The interface is no longer available", RNS.LOG_DEBUG)
*/
try {
#if CUSTOM
TRACEF("Transport::start: buffer capacity %d bytes", Persistence::_buffer.capacity());
if (RNS::Utilities::OS::read_file(destination_table_path, Persistence::_buffer) > 0) {
TRACEF("Transport::start: read: %d bytes", Persistence::_buffer.size());
#ifndef NDEBUG
// CBA DEBUG Dump path table
TRACEF("Transport::start: buffer addr: 0x%X", Persistence::_buffer.data());
TRACEF("Transport::start: buffer size %d bytes", Persistence::_buffer.size());
//TRACE("SERIALIZED: destination_table");
//TRACE(Persistence::_buffer.toString());
#endif
#ifdef USE_MSGPACK
DeserializationError error = deserializeMsgPack(Persistence::_document, Persistence::_buffer.data());
#else
DeserializationError error = deserializeJson(Persistence::_document, Persistence::_buffer.data());
#endif
TRACEF("Transport::start: doc size: %d bytes", Persistence::_buffer.size());
if (!error) {
// Calculate crc for dirty-checking before write
_destination_table_crc = Crc::crc32(0, Persistence::_buffer.data(), Persistence::_buffer.size());
_destination_table = Persistence::_document.as<std::map<Bytes, DestinationEntry>>();
#else // CUSTOM
// Calculate crc for dirty-checking before write
if (Persistence::deserialize(_destination_table, destination_table_path, _destination_table_crc) > 0) {
#endif // CUSTOM
TRACEF("Transport::start: successfully deserialized path table with %d entries", _destination_table.size());
std::vector<Bytes> invalid_paths;
for (auto& [destination_hash, destination_entry] : _destination_table) {
#ifndef NDEBUG
TRACEF("Transport::start: entry: %s = %s", destination_hash.toHex().c_str(), destination_entry.debugString().c_str());
#endif
// CBA If announce packet load fails then remove destination entry (it's useless without announce packet)
if (!destination_entry.announce_packet()) {
// remove destination
WARNINGF("Transport::start: removing invalid path to %s due to missing announce packet", destination_hash.toHex().c_str());
invalid_paths.push_back(destination_hash);
}
// CBA If receiving interface is not found then remove destination entry (it's useless without interface)
if (!destination_entry.receiving_interface()) {
// remove destination
WARNINGF("Transport::start: removing invalid path to %s due to missing receiving interface", destination_hash.toHex().c_str());
invalid_paths.push_back(destination_hash);
}
}
for (const auto& destination_hash : invalid_paths) {
_destination_table.erase(destination_hash);
}
return true;
}
else {
TRACE("Transport::start: failed to deserialize");
}
#if CUSTOM
}
else {
TRACE("Transport::start: destination table read failed");
}
#else // CUSTOM
#endif // CUSTOM
VERBOSEF("Loaded %d valid path table entries from storage", _destination_table.size());
}
catch (std::exception& e) {
ERRORF("Could not load destination table from storage, the contained exception was: %s", e.what());
}
}
#endif
return false;
}
/*static*/ bool Transport::write_path_table() {
DEBUG("Transport::write_path_table");
if (Transport::_owner.is_connected_to_shared_instance()) {
return true;
}
bool success = false;
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
if (_saving_path_table) {
double wait_interval = 0.2;
double wait_timeout = 5;
double wait_start = OS::time();
while (_saving_path_table) {
OS::sleep(wait_interval);
if (OS::time() > (wait_start + wait_timeout)) {
ERROR("Could not save path table to storage, waiting for previous save operation timed out.");
return false;
}
}
}
try {
_saving_path_table = true;
double save_start = OS::time();
DEBUGF("Saving %d path table entries to storage...", _destination_table.size());
/*p
serialised_destinations = []
for destination_hash in Transport.destination_table:
// Get the destination entry from the destination table
de = Transport.destination_table[destination_hash]
interface_hash = de[5].get_hash()
// Only store destination table entry if the associated
// interface is still active
interface = Transport.find_interface_from_hash(interface_hash)
if interface != None:
// Get the destination entry from the destination table
de = Transport.destination_table[destination_hash]
timestamp = de[0]
received_from = de[1]
hops = de[2]
expires = de[3]
random_blobs = de[4]
packet_hash = de[6].get_hash()
serialised_entry = [
destination_hash,
timestamp,
received_from,
hops,
expires,
random_blobs,
interface_hash,
packet_hash
]
serialised_destinations.append(serialised_entry)
Transport.cache(de[6], force_cache=True)
destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
file = open(destination_table_path, "wb")
file.write(umsgpack.packb(serialised_destinations))
file.close()
*/
#if CUSTOM
{
Persistence::_document.set(_destination_table);
TRACEF("Transport::write_path_table: doc size %d bytes", Persistence::_document.memoryUsage());
//size_t size = 8192;
size_t size = Persistence::_buffer.capacity();
TRACE("Transport::write_path_table: obtaining buffer size " + std::to_string(size) + " bytes");
uint8_t* buffer = Persistence::_buffer.writable(size);
TRACE("Transport::write_path_table: buffer addr: " + std::to_string((long)buffer));
#ifdef USE_MSGPACK
size_t length = serializeMsgPack(Persistence::_document, buffer, size);
#else
size_t length = serializeJson(Persistence::_document, buffer, size);
#endif
TRACEF("Transport::write_path_table: serialized %d bytes", length);
if (length < size) {
Persistence::_buffer.resize(length);
}
}
if (Persistence::_buffer.size() > 0) {
char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
#ifndef NDEBUG
// CBA DEBUG Dump path table
TRACE("Transport::write_path_table: buffer addr: " + std::to_string((long)Persistence::_buffer.data()));
TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence::_buffer.size()) + " bytes");
//TRACE("SERIALIZED: destination_table");
//TRACE(Persistence::_buffer.toString());
#endif
// Check crc to see if data has changed before writing
uint32_t crc = Crc::crc32(0, Persistence::_buffer.data(), Persistence::_buffer.size());
if (_destination_table_crc > 0 && crc == _destination_table_crc) {
TRACE("Transport::write_path_table: no change detected, skipping write");
}
else if (RNS::Utilities::OS::write_file(destination_table_path, Persistence::_buffer) == Persistence::_buffer.size()) {
TRACEF("Transport::write_path_table: wrote %d entries, %d bytes", _destination_table.size(), Persistence::_buffer.size());
_destination_table_crc = crc;
success = true;
#ifndef NDEBUG
// CBA DEBUG Dump path table
//TRACE("FILE: destination_table");
//if (OS::read_file("/destination_table", Persistence::_buffer) > 0) {
// TRACE(Persistence::_buffer.toString());
//}
#endif
}
else {
TRACE("Transport::write_path_table: write failed");
}
}
else {
TRACE("Transport::write_path_table: failed to serialize");
}
#else // CUSTOM
uint32_t crc = Persistence::crc(_destination_table);
if (_destination_table_crc > 0 && crc == _destination_table_crc) {
TRACE("Transport::write_path_table: no change detected, skipping write");
}
else {
TRACE("Transport::write_path_table: change detected, writing...");
char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
if (Persistence::serialize(_destination_table, destination_table_path, _destination_table_crc) > 0) {
TRACEF("Transport::write_path_table: wrote %d entries, %d bytes", _destination_table.size(), Persistence::_buffer.size());
success = true;
}
}
#endif // CUSTOM
if (success) {
double save_time = OS::time() - save_start;
if (save_time < 1.0) {
//DEBUG("Saved " + std::to_string(_destination_table.size()) + " path table entries in " + std::to_string(OS::round(save_time * 1000, 1)) + " ms");
DEBUGF("Saved %d path table entries in %d ms", _destination_table.size(), (int)(save_time*1000));
}
else {
//DEBUG("Saved " + std::to_string(_destination_table.size()) + " path table entries in " + std::to_string(OS::round(save_time, 1)) + " s");
DEBUGF("Saved %d path table entries in %d s", _destination_table.size(), save_time);
}
}
}
catch (std::exception& e) {
ERRORF("Could not save path table to storage, the contained exception was: %s", e.what());
}
#endif
_saving_path_table = false;
return success;
}
/*static*/ void Transport::read_tunnel_table() {
DEBUG("Transport::read_tunnel_table");
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
// TODO
/*p
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance:
serialised_tunnels = []
try:
file = open(tunnel_table_path, "rb")
serialised_tunnels = umsgpack.unpackb(file.read())
file.close()
for serialised_tunnel in serialised_tunnels:
tunnel_id = serialised_tunnel[0]
interface_hash = serialised_tunnel[1]
serialised_paths = serialised_tunnel[2]
expires = serialised_tunnel[3]
tunnel_paths = {}
for serialised_entry in serialised_paths:
destination_hash = serialised_entry[0]
timestamp = serialised_entry[1]
received_from = serialised_entry[2]
hops = serialised_entry[3]
expires = serialised_entry[4]
random_blobs = serialised_entry[5]
receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
announce_packet = Transport.get_cached_packet(serialised_entry[7])
if announce_packet != None:
announce_packet.unpack()
// We increase the hops, since reading a packet
// from cache is equivalent to receiving it again
// over an interface. It is cached with it's non-
// increased hop-count.
announce_packet.hops += 1
tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
tunnel_paths[destination_hash] = tunnel_path
tunnel = [tunnel_id, None, tunnel_paths, expires]
Transport.tunnels[tunnel_id] = tunnel
if len(Transport.destination_table) == 1:
specifier = "entry"
else:
specifier = "entries"
RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE)
except Exception as e:
RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
*/
#endif
}
/*static*/ void Transport::write_tunnel_table() {
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
// TODO
/*p
if not Transport.owner.is_connected_to_shared_instance:
if hasattr(Transport, "saving_tunnel_table"):
wait_interval = 0.2
wait_timeout = 5
wait_start = time.time()
while Transport.saving_tunnel_table:
time.sleep(wait_interval)
if time.time() > wait_start+wait_timeout:
RNS.log("Could not save tunnel table to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
return False
try:
Transport.saving_tunnel_table = True
save_start = time.time()
RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG)
serialised_tunnels = []
for tunnel_id in Transport.tunnels:
te = Transport.tunnels[tunnel_id]
interface = te[1]
tunnel_paths = te[2]
expires = te[3]
if interface != None:
interface_hash = interface.get_hash()
else:
interface_hash = None
serialised_paths = []
for destination_hash in tunnel_paths:
de = tunnel_paths[destination_hash]
timestamp = de[0]
received_from = de[1]
hops = de[2]
expires = de[3]
random_blobs = de[4]
packet_hash = de[6].get_hash()
serialised_entry = [
destination_hash,
timestamp,
received_from,
hops,
expires,
random_blobs,
interface_hash,
packet_hash
]
serialised_paths.append(serialised_entry)
Transport.cache(de[6], force_cache=True)
serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires]
serialised_tunnels.append(serialised_tunnel)
tunnels_path = RNS.Reticulum.storagepath+"/tunnels"
file = open(tunnels_path, "wb")
file.write(umsgpack.packb(serialised_tunnels))
file.close()
save_time = time.time() - save_start
if save_time < 1:
time_str = str(round(save_time*1000,2))+" ms"
else:
time_str = str(round(save_time,2))+" s"
RNS.log("Saved "+str(len(serialised_tunnels))+" tunnel table entries in "+time_str, RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
Transport.saving_tunnel_table = False
*/
#endif
}
/*static*/ void Transport::persist_data() {
TRACE("Transport::persist_data()");
write_packet_hashlist();
write_path_table();
write_tunnel_table();
}
/*static*/ void Transport::clean_caches() {
TRACE("Transport::clean_caches()");
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
// CBA Remove cached packets no longer in path list
std::list<std::string> files = OS::list_directory(Reticulum::_cachepath);
for (auto& file : files) {
TRACE("Transport::clean_caches: Checking for use of cached packet " + file);
bool found = false;
for (auto& [destination_hash, destination_entry] : _destination_table) {
if (file.compare(destination_entry._announce_packet.toHex()) == 0) {
found = true;
break;
}
}
if (!found) {
TRACE("Transport::clean_caches: No matching path found, removing cached packet " + file);
char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, file.c_str());
OS::remove_file(packet_cache_path);
}
}
#endif
}
/*static*/ void Transport::dump_stats() {
OS::dump_heap_stats();
size_t memory = OS::heap_available();
size_t flash = OS::storage_available();
if (_last_memory == 0) {
_last_memory = memory;
}
if (_last_flash == 0) {
_last_flash = flash;
}
// memory
// storage
// _destinations
// _destination_table
// _reverse_table
// _announce_table
// _held_announces
HEADF(LOG_VERBOSE, "mem: %u (%u%%) [%d] flash: %u (%u%%) [%d] paths: %u dsts: %u revr: %u annc: %u held: %u", memory, (int)((double)memory / (double)OS::heap_size() * 100.0), memory - _last_memory, flash, (int)((double)flash / (double)OS::storage_size() * 100.0), flash - _last_flash, _destination_table.size(), _destinations.size(), _reverse_table.size(), _announce_table.size(), _held_announces.size());
// _path_requests
// _discovery_path_requests
// _pending_local_path_requests
// _discovery_pr_tags
// _control_destinations
// _control_hashes
VERBOSEF("preqs: %u dpreqs: %u ppreqs: %u dprt: %u cdsts: %u chshs: %u", _path_requests.size(), _discovery_path_requests.size(), _pending_local_path_requests.size(), _discovery_pr_tags.size(), _control_destinations.size(), _control_hashes.size());
// _packet_hashlist
// _receipts
// _link_table
// _pending_links
// _active_links
// _tunnels
uint32_t destination_path_responses = 0;
for (auto& [destination_hash, destination] : _destinations) {
destination_path_responses += destination.path_responses().size();
}
uint32_t interface_announces = 0;
for (auto& [interface_hash, interface] : _interfaces) {
interface_announces += interface.announce_queue().size();
}
VERBOSEF("phl: %u rcp: %u lt: %u pl: %u al: %u tun: %u", _packet_hashlist.size(), _receipts.size(), _link_table.size(), _pending_links.size(), _active_links.size(), _tunnels.size());
VERBOSEF("pin: %u pout: %u padd: %u dpr: %u ikd: %u ia: %u\r\n", _packets_received, _packets_sent, _destinations_added, destination_path_responses, Identity::_known_destinations.size(), interface_announces);
_last_memory = memory;
_last_flash = flash;
}
/*static*/ void Transport::exit_handler() {
TRACE("Transport::exit_handler()");
if (!_owner.is_connected_to_shared_instance()) {
persist_data();
}
}
/*static*/ Destination Transport::find_destination_from_hash(const Bytes& destination_hash) {
TRACE("Transport::find_destination_from_hash: Searching for destination " + destination_hash.toHex());
#if defined(DESTINATIONS_SET)
for (const Destination& destination : _destinations) {
if (destination.get_hash() == destination_hash) {
TRACE("Transport::find_destination_from_hash: Found destination " + destination.toString());
return destination;
}
}
#elif defined(DESTINATIONS_MAP)
auto iter = _destinations.find(destination_hash);
if (iter != _destinations.end()) {
TRACE("Transport::find_destination_from_hash: Found destination " + (*iter).second.toString());
return (*iter).second;
}
#endif
return {Type::NONE};
}
/*static*/ void Transport::cull_path_table() {
TRACE("Transport::cull_path_table()");
if (_destination_table.size() > _path_table_maxsize) {
// TODO prune by age, or better yet by last use
/*
std::map<Bytes, DestinationEntry>::iterator iter = _destination_table.begin();
// naively erase from front of table
std::advance(iter, _destination_table.size() - _path_table_maxsize + 1);
_destination_table.erase(_destination_table.begin(), iter);
*/
/*
uint16_t count = 0;
std::set<DestinationEntry> sorted_values;
MapToValues(_destination_table, sorted_values);
for (auto& destination_entry : sorted_values) {
Packet announce_packet = destination_entry.announce_packet();
TRACE("Transport::cull_path_table: Removing destination " + announce_packet.destination_hash().toHex() + " from path table");
// Remove destination from path table
if (_destination_table.erase(announce_packet.destination_hash()) < 1) {
WARNING("Failed to remove destination " + announce_packet.destination_hash().toHex() + " from path table");
}
// Remove announce packet from packet table
//if (_packet_table.erase(destination_entry._announce_packet) < 1) {
// WARNING("Failed to remove packet " + destination_entry._announce_packet.toHex() + " from packet table");
//}
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
// Remove cached packet file
char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, destination_entry._announce_packet.toHex().c_str());
if (OS::file_exists(packet_cache_path)) {
OS::remove_file(packet_cache_path);
}
#endif
++count;
if (_destination_table.size() <= _path_table_maxsize) {
break;
}
}
DEBUG("Removed " + std::to_string(count) + " path(s) from path table");
*/
uint16_t count = 0;
std::vector<std::pair<Bytes,DestinationEntry>> sorted_pairs;
// Copy key/value pairs from map into vector
std::for_each(_destination_table.begin(), _destination_table.end(), [&](const std::pair<const Bytes, DestinationEntry>& ref) {
sorted_pairs.push_back(ref);
});
// Sort vector using specified comparator
std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair<Bytes,DestinationEntry> &left, const std::pair<Bytes,DestinationEntry> &right) {
return left.second._timestamp < right.second._timestamp;
});
// Iterate vector of sorted values
for (auto& [destination_hash, destination_entry] : sorted_pairs) {
TRACE("Transport::cull_path_table: Removing destination " + destination_hash.toHex() + " from path table");
// Remove destination from path table
if (_destination_table.erase(destination_hash) < 1) {
WARNING("Failed to remove destination " + destination_hash.toHex() + " from path table");
}
// Remove announce packet from packet table
//if (_packet_table.erase(destination_entry._announce_packet) < 1) {
// WARNING("Failed to remove packet " + destination_entry._announce_packet.toHex() + " from packet table");
//}
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
// Remove cached packet file
char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, destination_entry._announce_packet.toHex().c_str());
if (OS::file_exists(packet_cache_path)) {
OS::remove_file(packet_cache_path);
}
#endif
++count;
if (_destination_table.size() <= _path_table_maxsize) {
break;
}
}
DEBUG("Removed " + std::to_string(count) + " path(s) from path table");
}
}
/*static*/ uint16_t Transport::remove_reverse_entries(const std::vector<Bytes>& hashes) {
uint16_t count = 0;
for (const auto& truncated_packet_hash : hashes) {
_reverse_table.erase(truncated_packet_hash);
++count;
}
if (count > 0) {
TRACEF("Released %u reverse table entries", count);
}
return count;
}
/*static*/ uint16_t Transport::remove_links(const std::vector<Bytes>& hashes) {
uint16_t count = 0;
for (const auto& link_id : hashes) {
_link_table.erase(link_id);
++count;
}
if (count > 0) {
TRACEF("Released %u links", count);
}
return count;
}
/*static*/ uint16_t Transport::remove_paths(const std::vector<Bytes>& hashes) {
uint16_t count = 0;
for (const auto& destination_hash : hashes) {
//_destination_table.erase(destination_hash);
remove_path(destination_hash);
++count;
}
if (count > 0) {
TRACEF("Released %u paths", count);
}
return count;
}
/*static*/ uint16_t Transport::remove_discovery_path_requests(const std::vector<Bytes>& hashes) {
uint16_t count = 0;
for (const auto& destination_hash : hashes) {
_discovery_path_requests.erase(destination_hash);
++count;
}
if (count > 0) {
TRACEF("Released %u waiting path requests", count);
}
return count;
}
/*static*/ uint16_t Transport::remove_tunnels(const std::vector<Bytes>& hashes) {
uint16_t count = 0;
for (const auto& tunnel_id : hashes) {
_tunnels.erase(tunnel_id);
++count;
}
if (count > 0) {
TRACEF("Released %u tunnels", count);
}
return count;
}