mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-06-22 23:41:55 +00:00
70d4aa6be9
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged 0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's reimplementation; only the still-needed fixes ride on the pinned branches (PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 -- Packet link-proof callback, Identity short-sig guard, and the bz2 layer + decompress-on-receive in Resource::assemble()). Consumer-side changes: - platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API); -std=gnu++11 -> gnu++17 (upstream requires C++17). - Namespace all microReticulum includes (angle + quote) to <microReticulum/...> for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved. - Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto overrides with correct success/failure returns. - SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore. - Static Transport::get_path_table() -> path_table(); instance getter unchanged. - Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep lib/libbz2 as the ESP32 bzlib provider). - patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which silently broke the path store (no peer paths learned, all messaging blocked). Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable (no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC + bz2-compressed-Resource receive) passes 5/5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
184 lines
6.7 KiB
C++
184 lines
6.7 KiB
C++
#pragma once
|
|
|
|
#include <microReticulum/Interface.h>
|
|
#include <microReticulum/Identity.h>
|
|
#include <microReticulum/Bytes.h>
|
|
#include <microReticulum/Type.h>
|
|
#include "AutoInterfacePeer.h"
|
|
|
|
#ifdef ARDUINO
|
|
#include <WiFi.h>
|
|
#include <WiFiUdp.h>
|
|
#include <IPv6Address.h>
|
|
#include <lwip/ip6_addr.h>
|
|
#include <lwip/netdb.h>
|
|
#include <lwip/sockets.h>
|
|
#else
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <string>
|
|
#include <cstdint>
|
|
|
|
// AutoInterface - automatic peer discovery via IPv6 multicast
|
|
// Matches Python RNS AutoInterface behavior for interoperability
|
|
class AutoInterface : public RNS::InterfaceImpl {
|
|
|
|
public:
|
|
// Protocol constants (match Python RNS)
|
|
static const uint16_t DEFAULT_DISCOVERY_PORT = 29716;
|
|
static const uint16_t DEFAULT_DATA_PORT = 42671;
|
|
static constexpr const char* DEFAULT_GROUP_ID = "reticulum";
|
|
static constexpr double PEERING_TIMEOUT = 22.0; // seconds (matches Python RNS)
|
|
static constexpr double ANNOUNCE_INTERVAL = 1.6; // seconds (matches Python RNS)
|
|
static constexpr double MCAST_ECHO_TIMEOUT = 6.5; // seconds (matches Python RNS)
|
|
static constexpr double REVERSE_PEERING_INTERVAL = ANNOUNCE_INTERVAL * 3.25; // ~5.2 seconds
|
|
static constexpr double PEER_JOB_INTERVAL = 4.0; // seconds (matches Python RNS)
|
|
static const size_t DEQUE_SIZE = 48; // packet dedup window
|
|
static constexpr double DEQUE_TTL = 0.75; // seconds
|
|
static const uint32_t BITRATE_GUESS = 10 * 1000 * 1000;
|
|
static const uint16_t HW_MTU = 1196;
|
|
|
|
// Discovery token is full_hash(group_id + link_local_address) = 32 bytes
|
|
// Python RNS sends and expects the full 32-byte hash (HASHLENGTH//8 = 256//8 = 32)
|
|
static const size_t TOKEN_SIZE = 32;
|
|
|
|
public:
|
|
AutoInterface(const char* name = "AutoInterface");
|
|
virtual ~AutoInterface();
|
|
|
|
// Configuration (call before start())
|
|
void set_group_id(const std::string& group_id) { _group_id = group_id; }
|
|
void set_discovery_port(uint16_t port) { _discovery_port = port; }
|
|
void set_data_port(uint16_t port) { _data_port = port; }
|
|
void set_interface_name(const std::string& ifname) { _ifname = ifname; }
|
|
|
|
// InterfaceImpl overrides
|
|
virtual bool start() override;
|
|
virtual void stop() override;
|
|
virtual void loop() override;
|
|
|
|
virtual inline std::string toString() const override {
|
|
return "AutoInterface[" + _name + "/" + _group_id + "]";
|
|
}
|
|
|
|
// Getters for testing
|
|
const RNS::Bytes& get_discovery_token() const { return _discovery_token; }
|
|
const RNS::Bytes& get_multicast_address() const { return _multicast_address_bytes; }
|
|
size_t peer_count() const { return _peers.size(); }
|
|
|
|
// Carrier state tracking (matches Python RNS)
|
|
bool carrier_changed() {
|
|
bool changed = _carrier_changed;
|
|
_carrier_changed = false; // Clear flag on read
|
|
return changed;
|
|
}
|
|
void clear_carrier_changed() { _carrier_changed = false; }
|
|
bool is_timed_out() const { return _timed_out; }
|
|
|
|
protected:
|
|
virtual bool send_outgoing(const RNS::Bytes& data) override;
|
|
|
|
private:
|
|
// Discovery and addressing
|
|
void calculate_multicast_address();
|
|
void calculate_discovery_token();
|
|
bool get_link_local_address();
|
|
|
|
// Socket operations
|
|
bool setup_discovery_socket();
|
|
bool setup_unicast_discovery_socket();
|
|
bool setup_data_socket();
|
|
bool join_multicast_group();
|
|
|
|
// Main loop operations
|
|
void send_announce();
|
|
void process_discovery();
|
|
void process_unicast_discovery();
|
|
void send_reverse_peering();
|
|
void reverse_announce(AutoInterfacePeer& peer);
|
|
void process_data();
|
|
void check_echo_timeout();
|
|
void check_link_local_address();
|
|
|
|
// Peer management
|
|
#ifdef ARDUINO
|
|
void add_or_refresh_peer(const IPv6Address& addr, double timestamp);
|
|
#else
|
|
void add_or_refresh_peer(const struct in6_addr& addr, double timestamp);
|
|
#endif
|
|
void expire_stale_peers();
|
|
|
|
// Deduplication
|
|
bool is_duplicate(const RNS::Bytes& packet);
|
|
void add_to_deque(const RNS::Bytes& packet);
|
|
void expire_deque_entries();
|
|
|
|
// Configuration
|
|
std::string _group_id = DEFAULT_GROUP_ID;
|
|
uint16_t _discovery_port = DEFAULT_DISCOVERY_PORT;
|
|
uint16_t _unicast_discovery_port = DEFAULT_DISCOVERY_PORT + 1; // 29717
|
|
uint16_t _data_port = DEFAULT_DATA_PORT;
|
|
std::string _ifname; // Network interface name (e.g., "eth0", "wlan0")
|
|
|
|
// Computed values
|
|
RNS::Bytes _discovery_token; // 16 bytes
|
|
RNS::Bytes _multicast_address_bytes; // 16 bytes (IPv6)
|
|
struct in6_addr _multicast_address;
|
|
struct in6_addr _link_local_address;
|
|
std::string _link_local_address_str;
|
|
std::string _multicast_address_str; // For logging
|
|
bool _data_socket_ok = false; // Data socket initialized successfully
|
|
#ifdef ARDUINO
|
|
IPv6Address _link_local_ip; // ESP32: link-local as IPv6Address
|
|
IPv6Address _multicast_ip; // ESP32: multicast as IPv6Address
|
|
#endif
|
|
|
|
// Sockets
|
|
#ifdef ARDUINO
|
|
int _discovery_socket = -1; // Raw socket for IPv6 multicast discovery
|
|
int _unicast_discovery_socket = -1; // Raw socket for unicast discovery (reverse peering)
|
|
int _data_socket = -1; // Raw socket for IPv6 unicast data (WiFiUDP doesn't support IPv6)
|
|
unsigned int _if_index = 0; // Interface index for scope_id
|
|
#else
|
|
int _discovery_socket = -1;
|
|
int _unicast_discovery_socket = -1; // Socket for unicast discovery (reverse peering)
|
|
int _data_socket = -1;
|
|
unsigned int _if_index = 0; // Interface index for multicast
|
|
#endif
|
|
|
|
// Peers and state
|
|
std::vector<AutoInterfacePeer> _peers;
|
|
double _last_announce = 0;
|
|
double _last_peer_job = 0; // Timestamp of last peer job check
|
|
|
|
// Echo tracking (matches Python RNS multicast_echoes / initial_echoes)
|
|
double _last_multicast_echo = 0.0; // Timestamp of last own echo received
|
|
bool _initial_echo_received = false; // True once first echo received
|
|
bool _timed_out = false; // Current timeout state
|
|
bool _carrier_changed = false; // Flag for Transport layer notification
|
|
bool _firewall_warning_logged = false; // Track firewall warning (log once)
|
|
|
|
// Deduplication: pairs of (packet_hash, timestamp)
|
|
struct DequeEntry {
|
|
RNS::Bytes hash;
|
|
double timestamp;
|
|
};
|
|
std::deque<DequeEntry> _packet_deque;
|
|
|
|
// Receive buffer
|
|
RNS::Bytes _buffer;
|
|
|
|
// Diagnostic counters (printed periodically as INFO)
|
|
uint32_t _stat_announce_sent = 0;
|
|
uint32_t _stat_announce_send_fail = 0;
|
|
uint32_t _stat_discovery_rx = 0;
|
|
uint32_t _stat_discovery_rx_self = 0;
|
|
uint32_t _stat_data_rx = 0;
|
|
double _last_stats_log = 0;
|
|
};
|