Files
pyxis/lib/ble_interface/BLEPeerManager.cpp
torlando-tech 43a7e1088f BLE P2P stability: fix ODR violation, shutdown safety, connection robustness
Fix systemic One Definition Rule violation where BLEInterface.h included
headers from deps/microReticulum/src/BLE/ while .cpp files compiled
against local lib/ble_interface/ versions, causing struct layout mismatches
(PeerInfo field shifting corrupted conn_handle/mtu) and class layout
mismatches (BLEPeerManager member differences caused LoadProhibited crash).

Key fixes:
- Include local BLE headers instead of deps versions in BLEInterface.h
- Sync PeerInfo keepalive tracking fields and BLETypes constants with deps
- Shutdown re-entrancy guard and proper client cleanup via deinit(true)
- Host sync checks before scan, advertise, and connect operations
- Avoid deadlock by deferring _on_connected from NimBLE host task
- Duplicate identity detection, stale handle cross-check in keepalives
- Bounds validation on conn_handle in setPeerHandle/promoteToIdentityKeyed
- Periodic persist_data() call for display name persistence across reboots

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:24:45 -05:00

899 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file BLEPeerManager.cpp
* @brief BLE-Reticulum Protocol v2.2 peer management implementation
*
* Uses fixed-size pools instead of STL containers to eliminate heap fragmentation.
*/
#include "BLEPeerManager.h"
#include "Log.h"
#include <cmath>
#include <cstring>
namespace RNS { namespace BLE {
BLEPeerManager::BLEPeerManager() {
memset(_local_mac_addr.addr, 0, 6); // Initialize to zeros
// Initialize all pools to empty state
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
_peers_by_identity_pool[i].clear();
_peers_by_mac_only_pool[i].clear();
}
for (size_t i = 0; i < MAC_IDENTITY_POOL_SIZE; i++) {
_mac_to_identity_pool[i].clear();
}
for (size_t i = 0; i < MAX_CONN_HANDLES; i++) {
_handle_to_peer[i] = nullptr;
}
}
void BLEPeerManager::setLocalMac(const Bytes& mac) {
if (mac.size() >= Limits::MAC_SIZE) {
memcpy(_local_mac_addr.addr, mac.data(), Limits::MAC_SIZE);
DEBUG("BLEPeerManager: Local MAC set to " + _local_mac_addr.toString());
}
}
//=============================================================================
// Peer Discovery
//=============================================================================
bool BLEPeerManager::addDiscoveredPeer(const Bytes& mac_address, int8_t rssi, uint8_t address_type) {
if (mac_address.size() < Limits::MAC_SIZE) {
return false;
}
Bytes mac(mac_address.data(), Limits::MAC_SIZE);
double now = Utilities::OS::time();
// Check if this MAC maps to a known identity
Bytes identity = getIdentityForMac(mac);
if (identity.size() == Limits::IDENTITY_SIZE) {
// Update existing peer with identity
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identity);
if (slot) {
PeerInfo& peer = slot->peer;
// Check if blacklisted
if (peer.state == PeerState::BLACKLISTED && now < peer.blacklisted_until) {
return false;
}
peer.last_seen = now;
peer.rssi = rssi;
peer.address_type = address_type; // Update address type
// Exponential moving average for RSSI
peer.rssi_avg = static_cast<int8_t>(0.7f * peer.rssi_avg + 0.3f * rssi);
return true;
}
}
// Check if peer exists in MAC-only storage
PeerByMacSlot* mac_slot = findPeerByMacSlot(mac);
if (mac_slot) {
PeerInfo& peer = mac_slot->peer;
// Check if blacklisted
if (peer.state == PeerState::BLACKLISTED && now < peer.blacklisted_until) {
return false;
}
peer.last_seen = now;
peer.rssi = rssi;
peer.address_type = address_type; // Update address type
peer.rssi_avg = static_cast<int8_t>(0.7f * peer.rssi_avg + 0.3f * rssi);
return true;
}
// New peer - add to MAC-only storage
PeerByMacSlot* empty_slot = findEmptyPeerByMacSlot();
if (!empty_slot) {
WARNING("BLEPeerManager: MAC-only peer pool is full, cannot add new peer");
return false;
}
empty_slot->in_use = true;
empty_slot->mac_address = mac;
PeerInfo& peer = empty_slot->peer;
peer = PeerInfo(); // Reset to defaults
peer.mac_address = mac;
peer.address_type = address_type;
peer.state = PeerState::DISCOVERED;
peer.discovered_at = now;
peer.last_seen = now;
peer.rssi = rssi;
peer.rssi_avg = rssi;
char buf[80];
snprintf(buf, sizeof(buf), "BLEPeerManager: Discovered new peer %s RSSI %d",
BLEAddress(mac.data()).toString().c_str(), rssi);
DEBUG(buf);
return true;
}
bool BLEPeerManager::setPeerIdentity(const Bytes& mac_address, const Bytes& identity) {
if (mac_address.size() < Limits::MAC_SIZE || identity.size() != Limits::IDENTITY_SIZE) {
return false;
}
Bytes mac(mac_address.data(), Limits::MAC_SIZE);
// Check if peer exists in MAC-only storage
PeerByMacSlot* mac_slot = findPeerByMacSlot(mac);
if (mac_slot) {
promoteToIdentityKeyed(mac, identity);
return true;
}
// Check if peer already has identity (MAC might have changed)
PeerByIdentitySlot* identity_slot = findPeerByIdentitySlot(identity);
if (identity_slot) {
// Update MAC address mapping
PeerInfo& peer = identity_slot->peer;
// Remove old MAC mapping if different
if (peer.mac_address != mac) {
removeMacToIdentity(peer.mac_address);
peer.mac_address = mac;
setMacToIdentity(mac, identity);
}
return true;
}
// Peer not found
WARNING("BLEPeerManager: Cannot set identity for unknown peer");
return false;
}
bool BLEPeerManager::updatePeerMac(const Bytes& identity, const Bytes& new_mac) {
if (identity.size() != Limits::IDENTITY_SIZE || new_mac.size() < Limits::MAC_SIZE) {
return false;
}
Bytes mac(new_mac.data(), Limits::MAC_SIZE);
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identity);
if (!slot) {
return false;
}
PeerInfo& peer = slot->peer;
// Remove old MAC mapping
removeMacToIdentity(peer.mac_address);
// Update to new MAC
peer.mac_address = mac;
setMacToIdentity(mac, identity);
char buf[80];
snprintf(buf, sizeof(buf), "BLEPeerManager: Updated MAC for peer to %s",
BLEAddress(mac.data()).toString().c_str());
DEBUG(buf);
return true;
}
//=============================================================================
// Peer Lookup
//=============================================================================
PeerInfo* BLEPeerManager::getPeerByMac(const Bytes& mac_address) {
if (mac_address.size() < Limits::MAC_SIZE) return nullptr;
Bytes mac(mac_address.data(), Limits::MAC_SIZE);
// Check MAC-to-identity mapping first
Bytes identity = getIdentityForMac(mac);
if (identity.size() == Limits::IDENTITY_SIZE) {
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identity);
if (slot) {
return &slot->peer;
}
}
// Check MAC-only storage
PeerByMacSlot* mac_slot = findPeerByMacSlot(mac);
if (mac_slot) {
return &mac_slot->peer;
}
return nullptr;
}
const PeerInfo* BLEPeerManager::getPeerByMac(const Bytes& mac_address) const {
return const_cast<BLEPeerManager*>(this)->getPeerByMac(mac_address);
}
PeerInfo* BLEPeerManager::getPeerByIdentity(const Bytes& identity) {
if (identity.size() != Limits::IDENTITY_SIZE) return nullptr;
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identity);
if (slot) {
return &slot->peer;
}
return nullptr;
}
const PeerInfo* BLEPeerManager::getPeerByIdentity(const Bytes& identity) const {
return const_cast<BLEPeerManager*>(this)->getPeerByIdentity(identity);
}
PeerInfo* BLEPeerManager::getPeerByHandle(uint16_t conn_handle) {
// O(1) lookup using handle array
return getHandleToPeer(conn_handle);
}
const PeerInfo* BLEPeerManager::getPeerByHandle(uint16_t conn_handle) const {
return getHandleToPeer(conn_handle);
}
std::vector<PeerInfo*> BLEPeerManager::getConnectedPeers() {
std::vector<PeerInfo*> result;
result.reserve(PEERS_POOL_SIZE);
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use && _peers_by_identity_pool[i].peer.isConnected()) {
result.push_back(&_peers_by_identity_pool[i].peer);
}
}
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use && _peers_by_mac_only_pool[i].peer.isConnected()) {
result.push_back(&_peers_by_mac_only_pool[i].peer);
}
}
return result;
}
std::vector<PeerInfo*> BLEPeerManager::getAllPeers() {
std::vector<PeerInfo*> result;
result.reserve(PEERS_POOL_SIZE * 2);
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use) {
result.push_back(&_peers_by_identity_pool[i].peer);
}
}
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use) {
result.push_back(&_peers_by_mac_only_pool[i].peer);
}
}
return result;
}
//=============================================================================
// Connection Management
//=============================================================================
PeerInfo* BLEPeerManager::getBestConnectionCandidate() {
double now = Utilities::OS::time();
PeerInfo* best = nullptr;
float best_score = -1.0f;
auto checkPeer = [&](PeerInfo& peer) {
// Skip if already connected or connecting
if (peer.state != PeerState::DISCOVERED) {
return;
}
// Skip if blacklisted
if (peer.state == PeerState::BLACKLISTED && now < peer.blacklisted_until) {
return;
}
// Skip if we shouldn't initiate (MAC sorting)
if (!shouldInitiateConnection(peer.mac_address)) {
return;
}
if (peer.score > best_score) {
best_score = peer.score;
best = &peer;
}
};
// Check identity-keyed peers (unlikely to be DISCOVERED state, but possible after disconnect)
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use) {
checkPeer(_peers_by_identity_pool[i].peer);
}
}
// Check MAC-only peers (more common for connection candidates)
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use) {
checkPeer(_peers_by_mac_only_pool[i].peer);
}
}
return best;
}
bool BLEPeerManager::shouldInitiateConnection(const Bytes& peer_mac) const {
Bytes our_mac(_local_mac_addr.addr, Limits::MAC_SIZE);
return shouldInitiateConnection(our_mac, peer_mac);
}
bool BLEPeerManager::shouldInitiateConnection(const Bytes& our_mac, const Bytes& peer_mac) {
if (our_mac.size() < Limits::MAC_SIZE || peer_mac.size() < Limits::MAC_SIZE) {
return false;
}
// Check if our MAC is a random address (first byte >= 0xC0)
// Random addresses have the two MSBs of the first byte set (0b11xxxxxx)
// When using random addresses, MAC comparison is unreliable because our
// random MAC changes between restarts. In this case, always initiate
// connections and let the identity layer handle duplicate connections.
if (our_mac.data()[0] >= 0xC0) {
return true; // Always initiate with random address
}
// Lower MAC initiates connection (standard behavior for public addresses)
BLEAddress our_addr(our_mac.data());
BLEAddress peer_addr(peer_mac.data());
bool result = our_addr.isLowerThan(peer_addr);
char buf[100];
snprintf(buf, sizeof(buf), "BLEPeerManager::shouldInitiateConnection: our=%s peer=%s result=%s",
our_addr.toString().c_str(), peer_addr.toString().c_str(), result ? "yes" : "no");
DEBUG(buf);
return result;
}
void BLEPeerManager::connectionSucceeded(const Bytes& identifier) {
PeerInfo* peer = findPeer(identifier);
if (!peer) return;
peer->connection_successes++;
peer->consecutive_failures = 0;
peer->connected_at = Utilities::OS::time();
peer->state = PeerState::CONNECTED;
DEBUG("BLEPeerManager: Connection succeeded for peer");
}
void BLEPeerManager::connectionFailed(const Bytes& identifier) {
PeerInfo* peer = findPeer(identifier);
if (!peer) return;
// Clear handle mapping on disconnect
if (peer->conn_handle != 0xFFFF) {
clearHandleToPeer(peer->conn_handle);
peer->conn_handle = 0xFFFF;
}
peer->connection_failures++;
peer->consecutive_failures++;
peer->state = PeerState::DISCOVERED;
// Check if should blacklist
if (peer->consecutive_failures >= Limits::BLACKLIST_THRESHOLD) {
double duration = calculateBlacklistDuration(peer->consecutive_failures);
peer->blacklisted_until = Utilities::OS::time() + duration;
peer->state = PeerState::BLACKLISTED;
char buf[80];
snprintf(buf, sizeof(buf), "BLEPeerManager: Blacklisted peer for %.0fs after %u failures",
duration, peer->consecutive_failures);
WARNING(buf);
}
}
void BLEPeerManager::setPeerState(const Bytes& identifier, PeerState state) {
PeerInfo* peer = findPeer(identifier);
if (peer) {
peer->state = state;
}
}
void BLEPeerManager::setPeerHandle(const Bytes& identifier, uint16_t conn_handle) {
// Validate handle is in range (ESP32 NimBLE uses small handles)
if (conn_handle != 0xFFFF && conn_handle >= MAX_CONN_HANDLES) {
WARNING("BLEPeerManager: Rejecting invalid conn_handle=" +
std::to_string(conn_handle) + " (max=" +
std::to_string(MAX_CONN_HANDLES - 1) + ")");
return;
}
PeerInfo* peer = findPeer(identifier);
if (peer) {
// Remove old handle mapping if exists
if (peer->conn_handle != 0xFFFF && peer->conn_handle < MAX_CONN_HANDLES) {
clearHandleToPeer(peer->conn_handle);
}
peer->conn_handle = conn_handle;
// Add new handle mapping
if (conn_handle != 0xFFFF) {
setHandleToPeer(conn_handle, peer);
}
}
}
void BLEPeerManager::setPeerMTU(const Bytes& identifier, uint16_t mtu) {
PeerInfo* peer = findPeer(identifier);
if (peer) {
peer->mtu = mtu;
}
}
void BLEPeerManager::removePeer(const Bytes& identifier) {
// Try identity first
if (identifier.size() == Limits::IDENTITY_SIZE) {
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identifier);
if (slot) {
// Remove handle mapping
if (slot->peer.conn_handle != 0xFFFF) {
clearHandleToPeer(slot->peer.conn_handle);
}
// Remove MAC mapping
removeMacToIdentity(slot->peer.mac_address);
slot->clear();
return;
}
}
// Try MAC
if (identifier.size() >= Limits::MAC_SIZE) {
Bytes mac(identifier.data(), Limits::MAC_SIZE);
// Check if maps to identity
Bytes identity = getIdentityForMac(mac);
if (identity.size() == Limits::IDENTITY_SIZE) {
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identity);
if (slot) {
// Remove handle mapping
if (slot->peer.conn_handle != 0xFFFF) {
clearHandleToPeer(slot->peer.conn_handle);
}
slot->clear();
}
removeMacToIdentity(mac);
return;
}
// Check MAC-only
PeerByMacSlot* mac_slot = findPeerByMacSlot(mac);
if (mac_slot) {
// Remove handle mapping
if (mac_slot->peer.conn_handle != 0xFFFF) {
clearHandleToPeer(mac_slot->peer.conn_handle);
}
mac_slot->clear();
}
}
}
void BLEPeerManager::updateRssi(const Bytes& identifier, int8_t rssi) {
PeerInfo* peer = findPeer(identifier);
if (peer) {
peer->rssi = rssi;
peer->rssi_avg = static_cast<int8_t>(0.7f * peer->rssi_avg + 0.3f * rssi);
}
}
//=============================================================================
// Statistics
//=============================================================================
void BLEPeerManager::recordPacketSent(const Bytes& identifier) {
PeerInfo* peer = findPeer(identifier);
if (peer) {
peer->packets_sent++;
peer->last_activity = Utilities::OS::time();
}
}
void BLEPeerManager::recordPacketReceived(const Bytes& identifier) {
PeerInfo* peer = findPeer(identifier);
if (peer) {
peer->packets_received++;
peer->last_activity = Utilities::OS::time();
}
}
void BLEPeerManager::updateLastActivity(const Bytes& identifier) {
PeerInfo* peer = findPeer(identifier);
if (peer) {
peer->last_activity = Utilities::OS::time();
}
}
//=============================================================================
// Scoring & Blacklist
//=============================================================================
void BLEPeerManager::recalculateScores() {
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use) {
_peers_by_identity_pool[i].peer.score = calculateScore(_peers_by_identity_pool[i].peer);
}
}
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use) {
_peers_by_mac_only_pool[i].peer.score = calculateScore(_peers_by_mac_only_pool[i].peer);
}
}
}
void BLEPeerManager::checkBlacklistExpirations() {
double now = Utilities::OS::time();
auto checkAndClear = [now](PeerInfo& peer) {
if (peer.state == PeerState::BLACKLISTED && now >= peer.blacklisted_until) {
peer.state = PeerState::DISCOVERED;
peer.blacklisted_until = 0;
DEBUG("BLEPeerManager: Peer blacklist expired, restored to DISCOVERED");
}
};
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use) {
checkAndClear(_peers_by_identity_pool[i].peer);
}
}
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use) {
checkAndClear(_peers_by_mac_only_pool[i].peer);
}
}
}
//=============================================================================
// Counts & Limits
//=============================================================================
size_t BLEPeerManager::connectedCount() const {
size_t count = 0;
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use && _peers_by_identity_pool[i].peer.isConnected()) {
count++;
}
}
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use && _peers_by_mac_only_pool[i].peer.isConnected()) {
count++;
}
}
return count;
}
void BLEPeerManager::cleanupStalePeers(double max_age) {
double now = Utilities::OS::time();
// Check MAC-only peers (identity-keyed peers are more persistent)
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (!_peers_by_mac_only_pool[i].in_use) continue;
const PeerInfo& peer = _peers_by_mac_only_pool[i].peer;
// Only clean up DISCOVERED peers (not connected or connecting)
if (peer.state == PeerState::DISCOVERED) {
double age = now - peer.last_seen;
if (age > max_age) {
Bytes mac = _peers_by_mac_only_pool[i].mac_address;
_peers_by_mac_only_pool[i].clear();
char buf[80];
snprintf(buf, sizeof(buf), "BLEPeerManager: Removed stale peer %s",
BLEAddress(mac.data()).toString().c_str());
TRACE(buf);
}
}
}
}
//=============================================================================
// Private Methods
//=============================================================================
float BLEPeerManager::calculateScore(const PeerInfo& peer) const {
double now = Utilities::OS::time();
// RSSI component (60% weight)
float rssi_norm = normalizeRssi(peer.rssi_avg);
float rssi_score = Scoring::RSSI_WEIGHT * rssi_norm;
// History component (30% weight)
float history_score = 0.0f;
if (peer.connection_attempts > 0) {
float success_rate = static_cast<float>(peer.connection_successes) /
static_cast<float>(peer.connection_attempts);
history_score = Scoring::HISTORY_WEIGHT * success_rate;
} else {
// New peer: benefit of the doubt (50%)
history_score = Scoring::HISTORY_WEIGHT * 0.5f;
}
// Recency component (10% weight)
float recency_score = 0.0f;
double age = now - peer.last_seen;
if (age < 5.0) {
recency_score = Scoring::RECENCY_WEIGHT * 1.0f;
} else if (age < 30.0) {
// Linear decay from 1.0 to 0.0 over 25 seconds
recency_score = Scoring::RECENCY_WEIGHT * (1.0f - static_cast<float>((age - 5.0) / 25.0));
}
return rssi_score + history_score + recency_score;
}
float BLEPeerManager::normalizeRssi(int8_t rssi) const {
// Clamp to expected range
if (rssi < Scoring::RSSI_MIN) rssi = Scoring::RSSI_MIN;
if (rssi > Scoring::RSSI_MAX) rssi = Scoring::RSSI_MAX;
// Map to 0.0-1.0
return static_cast<float>(rssi - Scoring::RSSI_MIN) /
static_cast<float>(Scoring::RSSI_MAX - Scoring::RSSI_MIN);
}
double BLEPeerManager::calculateBlacklistDuration(uint8_t failures) const {
// Exponential backoff: 60s × min(2^(failures-3), 8)
if (failures < Limits::BLACKLIST_THRESHOLD) {
return 0;
}
uint8_t exponent = failures - Limits::BLACKLIST_THRESHOLD;
uint8_t multiplier = 1 << exponent; // 2^exponent
if (multiplier > Limits::BLACKLIST_MAX_MULTIPLIER) {
multiplier = Limits::BLACKLIST_MAX_MULTIPLIER;
}
return Timing::BLACKLIST_BASE_BACKOFF * multiplier;
}
PeerInfo* BLEPeerManager::findPeer(const Bytes& identifier) {
// Try as identity
if (identifier.size() == Limits::IDENTITY_SIZE) {
PeerByIdentitySlot* slot = findPeerByIdentitySlot(identifier);
if (slot) {
return &slot->peer;
}
}
// Try as MAC
if (identifier.size() >= Limits::MAC_SIZE) {
return getPeerByMac(identifier);
}
return nullptr;
}
void BLEPeerManager::promoteToIdentityKeyed(const Bytes& mac_address, const Bytes& identity) {
PeerByMacSlot* mac_slot = findPeerByMacSlot(mac_address);
if (!mac_slot) {
return;
}
// Check if there's already an identity-keyed entry for this peer
// (e.g. from a previous connection that was disconnected but not cleared)
PeerByIdentitySlot* identity_slot = findPeerByIdentitySlot(identity);
if (identity_slot) {
// Reuse existing slot — clear old handle mapping first
if (identity_slot->peer.conn_handle != 0xFFFF &&
identity_slot->peer.conn_handle < MAX_CONN_HANDLES) {
clearHandleToPeer(identity_slot->peer.conn_handle);
}
DEBUG("BLEPeerManager: Reusing existing identity slot for peer");
} else {
// Find an empty slot in identity pool
identity_slot = findEmptyPeerByIdentitySlot();
if (!identity_slot) {
WARNING("BLEPeerManager: Identity pool is full, cannot promote peer");
return;
}
}
// Copy peer info to identity pool
identity_slot->in_use = true;
identity_slot->identity_hash = identity;
identity_slot->peer = mac_slot->peer;
identity_slot->peer.identity = identity;
// Validate and update handle mapping to point to new location
if (identity_slot->peer.conn_handle != 0xFFFF) {
if (identity_slot->peer.conn_handle < MAX_CONN_HANDLES) {
setHandleToPeer(identity_slot->peer.conn_handle, &identity_slot->peer);
} else {
WARNING("BLEPeerManager: Promoted peer has invalid conn_handle=" +
std::to_string(identity_slot->peer.conn_handle) + ", clearing");
identity_slot->peer.conn_handle = 0xFFFF;
}
}
// Add MAC-to-identity mapping
setMacToIdentity(mac_address, identity);
// Clear MAC-only slot
mac_slot->clear();
DEBUG("BLEPeerManager: Promoted peer to identity-keyed storage");
}
//=============================================================================
// Pool Helper Methods - Peers by Identity
//=============================================================================
BLEPeerManager::PeerByIdentitySlot* BLEPeerManager::findPeerByIdentitySlot(const Bytes& identity) {
if (identity.size() != Limits::IDENTITY_SIZE) return nullptr;
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use &&
_peers_by_identity_pool[i].identity_hash == identity) {
return &_peers_by_identity_pool[i];
}
}
return nullptr;
}
const BLEPeerManager::PeerByIdentitySlot* BLEPeerManager::findPeerByIdentitySlot(const Bytes& identity) const {
return const_cast<BLEPeerManager*>(this)->findPeerByIdentitySlot(identity);
}
BLEPeerManager::PeerByIdentitySlot* BLEPeerManager::findEmptyPeerByIdentitySlot() {
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (!_peers_by_identity_pool[i].in_use) {
return &_peers_by_identity_pool[i];
}
}
return nullptr;
}
size_t BLEPeerManager::peersByIdentityCount() const {
size_t count = 0;
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_identity_pool[i].in_use) count++;
}
return count;
}
//=============================================================================
// Pool Helper Methods - Peers by MAC Only
//=============================================================================
BLEPeerManager::PeerByMacSlot* BLEPeerManager::findPeerByMacSlot(const Bytes& mac) {
if (mac.size() < Limits::MAC_SIZE) return nullptr;
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use &&
_peers_by_mac_only_pool[i].mac_address == mac) {
return &_peers_by_mac_only_pool[i];
}
}
return nullptr;
}
const BLEPeerManager::PeerByMacSlot* BLEPeerManager::findPeerByMacSlot(const Bytes& mac) const {
return const_cast<BLEPeerManager*>(this)->findPeerByMacSlot(mac);
}
BLEPeerManager::PeerByMacSlot* BLEPeerManager::findEmptyPeerByMacSlot() {
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (!_peers_by_mac_only_pool[i].in_use) {
return &_peers_by_mac_only_pool[i];
}
}
return nullptr;
}
size_t BLEPeerManager::peersByMacOnlyCount() const {
size_t count = 0;
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (_peers_by_mac_only_pool[i].in_use) count++;
}
return count;
}
//=============================================================================
// Pool Helper Methods - MAC to Identity Mapping
//=============================================================================
BLEPeerManager::MacToIdentitySlot* BLEPeerManager::findMacToIdentitySlot(const Bytes& mac) {
if (mac.size() < Limits::MAC_SIZE) return nullptr;
for (size_t i = 0; i < MAC_IDENTITY_POOL_SIZE; i++) {
if (_mac_to_identity_pool[i].in_use &&
_mac_to_identity_pool[i].mac_address == mac) {
return &_mac_to_identity_pool[i];
}
}
return nullptr;
}
const BLEPeerManager::MacToIdentitySlot* BLEPeerManager::findMacToIdentitySlot(const Bytes& mac) const {
return const_cast<BLEPeerManager*>(this)->findMacToIdentitySlot(mac);
}
BLEPeerManager::MacToIdentitySlot* BLEPeerManager::findEmptyMacToIdentitySlot() {
for (size_t i = 0; i < MAC_IDENTITY_POOL_SIZE; i++) {
if (!_mac_to_identity_pool[i].in_use) {
return &_mac_to_identity_pool[i];
}
}
return nullptr;
}
bool BLEPeerManager::setMacToIdentity(const Bytes& mac, const Bytes& identity) {
// Check if already exists
MacToIdentitySlot* existing = findMacToIdentitySlot(mac);
if (existing) {
existing->identity = identity;
return true;
}
// Find empty slot
MacToIdentitySlot* slot = findEmptyMacToIdentitySlot();
if (!slot) {
WARNING("BLEPeerManager: MAC-to-identity pool is full");
return false;
}
slot->in_use = true;
slot->mac_address = mac;
slot->identity = identity;
return true;
}
void BLEPeerManager::removeMacToIdentity(const Bytes& mac) {
MacToIdentitySlot* slot = findMacToIdentitySlot(mac);
if (slot) {
slot->clear();
}
}
Bytes BLEPeerManager::getIdentityForMac(const Bytes& mac) const {
const MacToIdentitySlot* slot = findMacToIdentitySlot(mac);
if (slot) {
return slot->identity;
}
return Bytes();
}
//=============================================================================
// Pool Helper Methods - Handle to Peer Mapping
//=============================================================================
void BLEPeerManager::setHandleToPeer(uint16_t handle, PeerInfo* peer) {
if (handle < MAX_CONN_HANDLES) {
_handle_to_peer[handle] = peer;
}
}
void BLEPeerManager::clearHandleToPeer(uint16_t handle) {
if (handle < MAX_CONN_HANDLES) {
_handle_to_peer[handle] = nullptr;
}
}
PeerInfo* BLEPeerManager::getHandleToPeer(uint16_t handle) {
if (handle < MAX_CONN_HANDLES) {
return _handle_to_peer[handle];
}
return nullptr;
}
const PeerInfo* BLEPeerManager::getHandleToPeer(uint16_t handle) const {
if (handle < MAX_CONN_HANDLES) {
return _handle_to_peer[handle];
}
return nullptr;
}
}} // namespace RNS::BLE