Files
RTNode-HeltecV4/lib/microReticulum/src/Transport.h
James L 1122e9a0ee v1.0.12: Fix link MTU clamping, echo-back prevention, oversized frame detection
MTU Clamping (Bug 8a): Clamp link MTU signalling in LINKREQUEST packets
when forwarding through transport node, matching Python reference impl.
Without this, TCP endpoints negotiate 8192-byte segments that exceed the
V3's 1064-byte HDLC buffer, causing silent truncation and permanent
resource transfer stalls at ~70%.

Fixed MTU declaration (Bug 8b): Set FIXED_MTU=true on TcpInterface so
Transport uses HW_MTU for clamping decisions.

Oversized frame detection (Bug 8c): Track truncated HDLC frames and
drop them with a diagnostic log instead of silently delivering corrupt
data to Transport.

Echo-back prevention (v1.0.10): Track which TCP client originated each
inbound frame and skip that client in send_outgoing() to prevent
flooding TCP buffers.

Register local client interface: Enable Transport forwarding of
announces, link packets, and proofs to TCP clients.

Document all discovered microReticulum bugs in MICRORETICULUM_BUGS.md.
2026-02-28 15:00:40 -05:00

506 lines
20 KiB
C++
Executable File

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