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
This commit is contained in:
504
lib/microReticulum/src/Transport.h
Executable file
504
lib/microReticulum/src/Transport.h
Executable file
@@ -0,0 +1,504 @@
|
||||
#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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user