mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-06-30 19:31:42 +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
488 lines
15 KiB
C++
488 lines
15 KiB
C++
/**
|
|
* @file BLEPeerManager.h
|
|
* @brief BLE-Reticulum Protocol v2.2 peer management
|
|
*
|
|
* Manages discovered and connected BLE peers with:
|
|
* - Peer scoring for connection prioritization
|
|
* - Blacklisting with exponential backoff
|
|
* - MAC address rotation handling via identity-based keying
|
|
* - Connection direction determination via MAC sorting
|
|
*
|
|
* Uses fixed-size pools instead of STL containers to eliminate heap fragmentation.
|
|
*/
|
|
#pragma once
|
|
|
|
#include "BLETypes.h"
|
|
#include <microReticulum/Bytes.h>
|
|
#include <microReticulum/Utilities/OS.h>
|
|
|
|
#include <cstdint>
|
|
|
|
namespace RNS { namespace BLE {
|
|
|
|
/**
|
|
* @brief Information about a discovered/connected peer
|
|
*/
|
|
struct PeerInfo {
|
|
// Addressing (both needed for MAC rotation handling)
|
|
Bytes mac_address; // Current 6-byte MAC address
|
|
Bytes identity; // 16-byte identity hash (stable key)
|
|
uint8_t address_type = 0; // BLE address type (0=public, 1=random)
|
|
|
|
// Connection state
|
|
PeerState state = PeerState::DISCOVERED;
|
|
bool is_central = false; // true if we are central (we initiated)
|
|
|
|
// Timing
|
|
double discovered_at = 0.0;
|
|
double last_seen = 0.0;
|
|
double last_activity = 0.0;
|
|
double connected_at = 0.0;
|
|
|
|
// Signal quality
|
|
int8_t rssi = Scoring::RSSI_MIN;
|
|
int8_t rssi_avg = Scoring::RSSI_MIN; // Smoothed average
|
|
|
|
// Statistics for scoring
|
|
uint32_t packets_sent = 0;
|
|
uint32_t packets_received = 0;
|
|
uint32_t connection_attempts = 0;
|
|
uint32_t connection_successes = 0;
|
|
uint32_t connection_failures = 0;
|
|
|
|
// Blacklist tracking
|
|
uint8_t consecutive_failures = 0;
|
|
double blacklisted_until = 0.0;
|
|
|
|
// Keepalive failure tracking
|
|
uint8_t consecutive_keepalive_failures = 0;
|
|
static constexpr uint8_t MAX_KEEPALIVE_FAILURES = 3;
|
|
|
|
// BLE connection handle (platform-specific)
|
|
uint16_t conn_handle = 0xFFFF;
|
|
|
|
// MTU for this peer
|
|
uint16_t mtu = MTU::MINIMUM;
|
|
|
|
// Computed score (cached)
|
|
float score = 0.0f;
|
|
|
|
// Check if peer has known identity
|
|
bool hasIdentity() const { return identity.size() == Limits::IDENTITY_SIZE; }
|
|
|
|
// Check if connected
|
|
bool isConnected() const {
|
|
return state == PeerState::CONNECTED ||
|
|
state == PeerState::HANDSHAKING;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Manages BLE peers for the BLEInterface
|
|
*
|
|
* Uses fixed-size pools to eliminate heap fragmentation:
|
|
* - _peers_pool: Stores all peer info (max 8 slots)
|
|
* - _mac_to_identity_pool: Maps MAC addresses to identities (max 8 slots)
|
|
* - _handle_to_peer: Fixed array indexed by connection handle (max 8)
|
|
*/
|
|
class BLEPeerManager {
|
|
public:
|
|
//=========================================================================
|
|
// Pool Configuration
|
|
//=========================================================================
|
|
static constexpr size_t PEERS_POOL_SIZE = 8;
|
|
static constexpr size_t MAC_IDENTITY_POOL_SIZE = 8;
|
|
static constexpr size_t MAX_CONN_HANDLES = 8;
|
|
|
|
/**
|
|
* @brief Slot for storing peer info (keyed by identity)
|
|
*/
|
|
struct PeerByIdentitySlot {
|
|
bool in_use = false;
|
|
Bytes identity_hash; // 16-byte identity key
|
|
PeerInfo peer; // value
|
|
|
|
void clear() {
|
|
in_use = false;
|
|
identity_hash.clear();
|
|
peer = PeerInfo();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Slot for storing peer info (keyed by MAC only, no identity yet)
|
|
*/
|
|
struct PeerByMacSlot {
|
|
bool in_use = false;
|
|
Bytes mac_address; // 6-byte MAC key
|
|
PeerInfo peer; // value
|
|
|
|
void clear() {
|
|
in_use = false;
|
|
mac_address.clear();
|
|
peer = PeerInfo();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Slot for MAC to identity mapping
|
|
*/
|
|
struct MacToIdentitySlot {
|
|
bool in_use = false;
|
|
Bytes mac_address; // 6-byte MAC key
|
|
Bytes identity; // 16-byte identity value
|
|
|
|
void clear() {
|
|
in_use = false;
|
|
mac_address.clear();
|
|
identity.clear();
|
|
}
|
|
};
|
|
|
|
BLEPeerManager();
|
|
|
|
/**
|
|
* @brief Set our local MAC address (for connection direction decisions)
|
|
*/
|
|
void setLocalMac(const Bytes& mac);
|
|
|
|
/**
|
|
* @brief Get our local MAC address as BLEAddress (no heap allocation)
|
|
*/
|
|
BLEAddress getLocalMac() const { return _local_mac_addr; }
|
|
|
|
//=========================================================================
|
|
// Peer Discovery
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Register a newly discovered peer from BLE scan
|
|
*
|
|
* @param mac_address 6-byte MAC address
|
|
* @param rssi Signal strength
|
|
* @param address_type BLE address type (0=public, 1=random)
|
|
* @return true if peer was added or updated (not blacklisted)
|
|
*/
|
|
bool addDiscoveredPeer(const Bytes& mac_address, int8_t rssi, uint8_t address_type = 0);
|
|
|
|
/**
|
|
* @brief Update peer identity after handshake completion
|
|
*
|
|
* @param mac_address Current MAC address
|
|
* @param identity 16-byte identity hash
|
|
* @return true if peer was found and updated
|
|
*/
|
|
bool setPeerIdentity(const Bytes& mac_address, const Bytes& identity);
|
|
|
|
/**
|
|
* @brief Update peer MAC address (when identity already known but MAC rotated)
|
|
*
|
|
* @param identity 16-byte identity hash
|
|
* @param new_mac New 6-byte MAC address
|
|
* @return true if peer was found and updated
|
|
*/
|
|
bool updatePeerMac(const Bytes& identity, const Bytes& new_mac);
|
|
|
|
//=========================================================================
|
|
// Peer Lookup
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Get peer info by MAC address
|
|
* @return Pointer to PeerInfo or nullptr if not found
|
|
*/
|
|
PeerInfo* getPeerByMac(const Bytes& mac_address);
|
|
const PeerInfo* getPeerByMac(const Bytes& mac_address) const;
|
|
|
|
/**
|
|
* @brief Get peer info by identity
|
|
* @return Pointer to PeerInfo or nullptr if not found
|
|
*/
|
|
PeerInfo* getPeerByIdentity(const Bytes& identity);
|
|
const PeerInfo* getPeerByIdentity(const Bytes& identity) const;
|
|
|
|
/**
|
|
* @brief Get peer info by connection handle
|
|
* @return Pointer to PeerInfo or nullptr if not found
|
|
*/
|
|
PeerInfo* getPeerByHandle(uint16_t conn_handle);
|
|
const PeerInfo* getPeerByHandle(uint16_t conn_handle) const;
|
|
|
|
/**
|
|
* @brief Get all connected peers
|
|
*/
|
|
std::vector<PeerInfo*> getConnectedPeers();
|
|
|
|
/**
|
|
* @brief Get all peers (for iteration)
|
|
*/
|
|
std::vector<PeerInfo*> getAllPeers();
|
|
|
|
//=========================================================================
|
|
// Connection Management
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Get best peer to connect to (highest score, not blacklisted)
|
|
* @return Pointer to best peer or nullptr if none available
|
|
*/
|
|
PeerInfo* getBestConnectionCandidate();
|
|
|
|
/**
|
|
* @brief Check if we should initiate connection to a peer (MAC sorting rule)
|
|
*
|
|
* Lower MAC address should be the initiator (central).
|
|
* @param peer_mac The peer's MAC address
|
|
* @return true if we should initiate (our MAC < peer MAC)
|
|
*/
|
|
bool shouldInitiateConnection(const Bytes& peer_mac) const;
|
|
|
|
/**
|
|
* @brief Static version for use without instance
|
|
*/
|
|
static bool shouldInitiateConnection(const Bytes& our_mac, const Bytes& peer_mac);
|
|
|
|
/**
|
|
* @brief Mark peer connection as successful
|
|
*/
|
|
void connectionSucceeded(const Bytes& identifier);
|
|
|
|
/**
|
|
* @brief Mark peer connection as failed
|
|
*/
|
|
void connectionFailed(const Bytes& identifier);
|
|
|
|
/**
|
|
* @brief Update peer state
|
|
*/
|
|
void setPeerState(const Bytes& identifier, PeerState state);
|
|
|
|
/**
|
|
* @brief Set peer connection handle
|
|
*/
|
|
void setPeerHandle(const Bytes& identifier, uint16_t conn_handle);
|
|
|
|
/**
|
|
* @brief Set peer MTU
|
|
*/
|
|
void setPeerMTU(const Bytes& identifier, uint16_t mtu);
|
|
|
|
/**
|
|
* @brief Remove a peer
|
|
*/
|
|
void removePeer(const Bytes& identifier);
|
|
|
|
/**
|
|
* @brief Update peer RSSI
|
|
*/
|
|
void updateRssi(const Bytes& identifier, int8_t rssi);
|
|
|
|
//=========================================================================
|
|
// Statistics
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Record packet sent to peer
|
|
*/
|
|
void recordPacketSent(const Bytes& identifier);
|
|
|
|
/**
|
|
* @brief Record packet received from peer
|
|
*/
|
|
void recordPacketReceived(const Bytes& identifier);
|
|
|
|
/**
|
|
* @brief Update last activity time for peer
|
|
*/
|
|
void updateLastActivity(const Bytes& identifier);
|
|
|
|
//=========================================================================
|
|
// Scoring & Blacklist
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Recalculate scores for all peers
|
|
*
|
|
* Should be called periodically or after significant changes.
|
|
*/
|
|
void recalculateScores();
|
|
|
|
/**
|
|
* @brief Check blacklist expirations and restore peers
|
|
*/
|
|
void checkBlacklistExpirations();
|
|
|
|
//=========================================================================
|
|
// Counts & Limits
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Get current connected peer count
|
|
*/
|
|
size_t connectedCount() const;
|
|
|
|
/**
|
|
* @brief Get total peer count
|
|
*/
|
|
size_t totalPeerCount() const { return peersByIdentityCount() + peersByMacOnlyCount(); }
|
|
|
|
/**
|
|
* @brief Check if we can accept more connections
|
|
*/
|
|
bool canAcceptConnection() const { return connectedCount() < Limits::MAX_PEERS; }
|
|
|
|
/**
|
|
* @brief Clean up stale discovered peers
|
|
* @param max_age Maximum age in seconds for discovered (unconnected) peers
|
|
*/
|
|
void cleanupStalePeers(double max_age = Timing::PEER_TIMEOUT);
|
|
|
|
private:
|
|
/**
|
|
* @brief Calculate peer score using v2.2 formula
|
|
*/
|
|
float calculateScore(const PeerInfo& peer) const;
|
|
|
|
/**
|
|
* @brief Normalize RSSI to 0.0-1.0 range
|
|
*/
|
|
float normalizeRssi(int8_t rssi) const;
|
|
|
|
/**
|
|
* @brief Calculate blacklist duration for given failure count
|
|
*/
|
|
double calculateBlacklistDuration(uint8_t failures) const;
|
|
|
|
/**
|
|
* @brief Find peer by any identifier (MAC or identity)
|
|
*/
|
|
PeerInfo* findPeer(const Bytes& identifier);
|
|
|
|
/**
|
|
* @brief Move peer from MAC-only to identity-keyed storage
|
|
*/
|
|
void promoteToIdentityKeyed(const Bytes& mac_address, const Bytes& identity);
|
|
|
|
//=========================================================================
|
|
// Pool Helper Methods - Peers by Identity
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Find slot by identity key
|
|
* @return Pointer to slot or nullptr if not found
|
|
*/
|
|
PeerByIdentitySlot* findPeerByIdentitySlot(const Bytes& identity);
|
|
const PeerByIdentitySlot* findPeerByIdentitySlot(const Bytes& identity) const;
|
|
|
|
/**
|
|
* @brief Find an empty slot in the identity pool
|
|
* @return Pointer to empty slot or nullptr if pool is full
|
|
*/
|
|
PeerByIdentitySlot* findEmptyPeerByIdentitySlot();
|
|
|
|
/**
|
|
* @brief Get count of peers by identity
|
|
*/
|
|
size_t peersByIdentityCount() const;
|
|
|
|
//=========================================================================
|
|
// Pool Helper Methods - Peers by MAC Only
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Find slot by MAC key
|
|
* @return Pointer to slot or nullptr if not found
|
|
*/
|
|
PeerByMacSlot* findPeerByMacSlot(const Bytes& mac);
|
|
const PeerByMacSlot* findPeerByMacSlot(const Bytes& mac) const;
|
|
|
|
/**
|
|
* @brief Find an empty slot in the MAC-only pool
|
|
* @return Pointer to empty slot or nullptr if pool is full
|
|
*/
|
|
PeerByMacSlot* findEmptyPeerByMacSlot();
|
|
|
|
/**
|
|
* @brief Get count of peers by MAC only
|
|
*/
|
|
size_t peersByMacOnlyCount() const;
|
|
|
|
//=========================================================================
|
|
// Pool Helper Methods - MAC to Identity Mapping
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Find MAC-to-identity mapping slot by MAC
|
|
* @return Pointer to slot or nullptr if not found
|
|
*/
|
|
MacToIdentitySlot* findMacToIdentitySlot(const Bytes& mac);
|
|
const MacToIdentitySlot* findMacToIdentitySlot(const Bytes& mac) const;
|
|
|
|
/**
|
|
* @brief Find an empty slot in the MAC-to-identity pool
|
|
* @return Pointer to empty slot or nullptr if pool is full
|
|
*/
|
|
MacToIdentitySlot* findEmptyMacToIdentitySlot();
|
|
|
|
/**
|
|
* @brief Add or update MAC-to-identity mapping
|
|
* @return true if successful, false if pool is full
|
|
*/
|
|
bool setMacToIdentity(const Bytes& mac, const Bytes& identity);
|
|
|
|
/**
|
|
* @brief Remove MAC-to-identity mapping
|
|
*/
|
|
void removeMacToIdentity(const Bytes& mac);
|
|
|
|
/**
|
|
* @brief Get identity for a MAC from the mapping pool
|
|
* @return Identity or empty Bytes if not found
|
|
*/
|
|
Bytes getIdentityForMac(const Bytes& mac) const;
|
|
|
|
//=========================================================================
|
|
// Pool Helper Methods - Handle to Peer Mapping
|
|
//=========================================================================
|
|
|
|
/**
|
|
* @brief Set handle-to-peer mapping
|
|
*/
|
|
void setHandleToPeer(uint16_t handle, PeerInfo* peer);
|
|
|
|
/**
|
|
* @brief Clear handle-to-peer mapping
|
|
*/
|
|
void clearHandleToPeer(uint16_t handle);
|
|
|
|
/**
|
|
* @brief Get peer for handle
|
|
* @return Pointer to peer or nullptr if not found
|
|
*/
|
|
PeerInfo* getHandleToPeer(uint16_t handle);
|
|
const PeerInfo* getHandleToPeer(uint16_t handle) const;
|
|
|
|
//=========================================================================
|
|
// Fixed-size Pool Storage
|
|
//=========================================================================
|
|
|
|
// Peers with known identity (keyed by identity)
|
|
PeerByIdentitySlot _peers_by_identity_pool[PEERS_POOL_SIZE];
|
|
|
|
// Peers without identity yet (keyed by MAC)
|
|
PeerByMacSlot _peers_by_mac_only_pool[PEERS_POOL_SIZE];
|
|
|
|
// MAC to identity lookup for peers with identity
|
|
MacToIdentitySlot _mac_to_identity_pool[MAC_IDENTITY_POOL_SIZE];
|
|
|
|
// Connection handle to peer pointer for O(1) lookup
|
|
// Index is the connection handle (must be < MAX_CONN_HANDLES)
|
|
// nullptr means no mapping for that handle
|
|
PeerInfo* _handle_to_peer[MAX_CONN_HANDLES];
|
|
|
|
// Our own MAC address (stored as plain struct to avoid Bytes heap corruption in PSRAM)
|
|
BLEAddress _local_mac_addr;
|
|
};
|
|
|
|
}} // namespace RNS::BLE
|