Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b8d2963
Don't crash on garbled announce packets
Astrrra Feb 11, 2026
399d906
Proper MTU for LoRa and longer path request waits
Astrrra Feb 11, 2026
3d2aa4e
Inverted logic in LINKIDENTIFY handler
Astrrra Feb 11, 2026
5fd96d0
Fix use-after-free in timeout handling and misc
Astrrra Feb 11, 2026
12dae62
Move Transport pools from BSS to PSRAM
torlando-tech Feb 19, 2026
36f4992
BLE protocol interop fixes for Columba compatibility
torlando-tech Feb 19, 2026
f7a8d80
Memory stability: fixed buffer app_data, BytesPool PSRAM, deferred Me…
torlando-tech Feb 19, 2026
d11aaaa
BLE interop: Link callbacks, packet tracking, transport logging
torlando-tech Feb 19, 2026
8c076d0
Update example platformio.ini and main.cpp references
torlando-tech Feb 19, 2026
b624978
Throttle known destinations saves to reduce SPIFFS blocking
torlando-tech Feb 22, 2026
17244c1
Route link packets only through link's attached interface
torlando-tech Feb 22, 2026
89d0aae
BLE: bounds validation for conn_handle, add writeCharacteristic API
torlando-tech Feb 23, 2026
2e2322b
Identity persistence: atomic save, fast persist, corrupt file recovery
torlando-tech Feb 23, 2026
12ef1ae
Identity persistence: yield callback + 60s dirty interval
torlando-tech Feb 23, 2026
8532c0d
Persist only contact destinations to flash, not network announces
torlando-tech Feb 23, 2026
a53cc10
LXMF E2E interop tests: bidirectional C++ <-> Python wire-format veri…
torlando-tech Mar 2, 2026
ca355e5
Fix propagation sync: msgpack interop, Resource response routing, tra…
torlando-tech Mar 3, 2026
be4cbd9
Fix LXMF field key encoding and OPPORTUNISTIC delivery threshold
torlando-tech Mar 6, 2026
efdc7a1
Fix Bytes({key_int}) constructor bug in LXMF field unpacking
torlando-tech Mar 6, 2026
ce46f66
Guard missing packed payload in message loader
Mar 31, 2026
0092955
Harden packed message handling
Apr 1, 2026
055454e
Optimize conversation list hot path
Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/link/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,7 @@ void cleanup_handler(int signum) {
// starts up the desired program mode.
int main(int argc, char *argv[]) {

#if defined(MEM_LOG)
#if defined(RNS_MEM_LOG)
RNS::loglevel(RNS::LOG_MEM);
#else
// Use DEBUG to see resource handling
Expand Down
2 changes: 1 addition & 1 deletion examples/link/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ build_type = debug
build_flags =
-DRNS_USE_ALLOCATOR=1
-DRNS_USE_TLSF=1
;-DMEM_LOG
;-DRNS_MEM_LOG
lib_deps =
ArduinoJson@^7.4.2
MsgPack@^0.4.2
Expand Down
2 changes: 1 addition & 1 deletion examples/lora_announce/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ monitor_speed = 115200
upload_speed = 921600
build_type = debug
build_flags =
;-DMEM_LOG=1
;-DRNS_MEM_LOG=1
-DRNS_USE_ALLOCATOR=1
-DRNS_USE_ALLOCATOR=1
-DRNS_USE_TLSF=1
Expand Down
2 changes: 1 addition & 1 deletion examples/udp_announce/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ void setup() {
}
#endif

#if defined(MEM_LOG)
#if defined(RNS_MEM_LOG)
RNS::loglevel(RNS::LOG_MEM);
#else
//RNS::loglevel(RNS::LOG_WARNING);
Expand Down
2 changes: 1 addition & 1 deletion examples/udp_announce/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ build_type = debug
build_flags =
-DRNS_USE_ALLOCATOR=1
-DRNS_USE_TLSF=1
;-DMEM_LOG
;-DRNS_MEM_LOG
lib_deps =
ArduinoJson@^6.21.3
https://github.com/attermann/Crypto.git
Expand Down
4 changes: 4 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ lib_deps =
${env.lib_deps}
lib_compat_mode = off
extra_scripts = pre:link_bz2.py
src_filter =
+<*>
-<UI/>
-<BLE/platforms/>

[env:native20]
platform = native
Expand Down
11 changes: 6 additions & 5 deletions src/BLE/BLEFragmenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ std::vector<Bytes> BLEFragmenter::fragment(const Bytes& data, uint16_t sequence_

// Handle empty data case
if (data.size() == 0) {
// Single empty END fragment
fragments.push_back(createFragment(Fragment::END, sequence_base, 1, Bytes()));
// Single empty LONE fragment
fragments.push_back(createFragment(Fragment::LONE, sequence_base, 1, Bytes()));
return fragments;
}

Expand All @@ -69,8 +69,8 @@ std::vector<Bytes> BLEFragmenter::fragment(const Bytes& data, uint16_t sequence_
// Determine fragment type
Fragment::Type type;
if (total_fragments == 1) {
// Single fragment - use END type
type = Fragment::END;
// Single fragment - use LONE type
type = Fragment::LONE;
} else if (i == 0) {
// First of multiple fragments
type = Fragment::START;
Expand Down Expand Up @@ -133,7 +133,8 @@ bool BLEFragmenter::parseHeader(const Bytes& fragment, Fragment::Type& type,

// Byte 0: Type
uint8_t type_byte = ptr[0];
if (type_byte != Fragment::START &&
if (type_byte != Fragment::LONE &&
type_byte != Fragment::START &&
type_byte != Fragment::CONTINUE &&
type_byte != Fragment::END) {
return false;
Expand Down
32 changes: 30 additions & 2 deletions src/BLE/BLEPeerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,18 @@ void BLEPeerManager::setPeerState(const Bytes& identifier, PeerState state) {
}

void BLEPeerManager::setPeerHandle(const Bytes& identifier, uint16_t conn_handle) {
// Validate handle is in range (ESP32 NimBLE uses 0-7)
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) {
if (peer->conn_handle != 0xFFFF && peer->conn_handle < MAX_CONN_HANDLES) {
clearHandleToPeer(peer->conn_handle);
}
peer->conn_handle = conn_handle;
Expand Down Expand Up @@ -586,6 +594,20 @@ void BLEPeerManager::cleanupStalePeers(double max_age) {
}
}
}

// Zombie detection: connected peers with no recent activity
for (size_t i = 0; i < PEERS_POOL_SIZE; i++) {
if (!_peers_by_identity_pool[i].in_use) continue;

PeerInfo& peer = _peers_by_identity_pool[i].peer;
if (peer.isConnected() && peer.last_activity > 0) {
double idle = now - peer.last_activity;
if (idle > Timing::ZOMBIE_TIMEOUT) {
WARNING("BLEPeerManager: Zombie peer detected, marking for disconnect");
peer.state = PeerState::DISCONNECTING;
}
}
}
}

//=============================================================================
Expand Down Expand Up @@ -686,7 +708,13 @@ void BLEPeerManager::promoteToIdentityKeyed(const Bytes& mac_address, const Byte

// Update handle mapping to point to new location
if (identity_slot->peer.conn_handle != 0xFFFF) {
setHandleToPeer(identity_slot->peer.conn_handle, &identity_slot->peer);
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
Expand Down
4 changes: 4 additions & 0 deletions src/BLE/BLEPeerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ struct PeerInfo {
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;

Expand Down
9 changes: 9 additions & 0 deletions src/BLE/BLEPlatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ class IBLEPlatform {
*/
virtual bool write(uint16_t conn_handle, const Bytes& data, bool response = true) = 0;

/**
* @brief Write to a specific characteristic by handle
*/
virtual bool writeCharacteristic(uint16_t conn_handle, uint16_t char_handle,
const Bytes& data, bool response = true) {
(void)char_handle;
return write(conn_handle, data, response);
}

/**
* @brief Read from a characteristic
*
Expand Down
10 changes: 10 additions & 0 deletions src/BLE/BLEReassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ bool BLEReassembler::processFragment(const Bytes& peer_identity, const Bytes& fr

double now = Utilities::OS::time();

// Handle LONE fragment - single complete message, no reassembly needed
if (type == Fragment::LONE) {
Bytes payload = BLEFragmenter::extractPayload(fragment);
TRACE("BLEReassembler: LONE fragment, delivering immediately");
if (_reassembly_callback) {
_reassembly_callback(peer_identity, payload);
}
return true;
}

// Handle START fragment - begins a new reassembly
if (type == Fragment::START) {
// Clear any existing incomplete reassembly for this peer
Expand Down
11 changes: 7 additions & 4 deletions src/BLE/BLETypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ namespace UUID {
//=============================================================================

namespace MTU {
static constexpr uint16_t ATT_OVERHEAD = 3; // ATT protocol header overhead
static constexpr uint16_t REQUESTED = 517; // Request maximum MTU (BLE 5.0)
static constexpr uint16_t MINIMUM = 23; // BLE 4.0 minimum MTU
static constexpr uint16_t INITIAL = 185; // Conservative default (BLE 4.2)
static constexpr uint16_t ATT_OVERHEAD = 3; // ATT protocol header overhead
static constexpr uint16_t INITIAL = 185 - ATT_OVERHEAD; // Conservative default (BLE 4.2), minus ATT overhead
}

//=============================================================================
Expand All @@ -66,11 +66,13 @@ namespace Timing {
static constexpr double KEEPALIVE_INTERVAL = 15.0; // Seconds between keepalives
static constexpr double REASSEMBLY_TIMEOUT = 30.0; // Seconds to complete reassembly
static constexpr double CONNECTION_TIMEOUT = 30.0; // Seconds to establish connection
static constexpr double HANDSHAKE_TIMEOUT = 10.0; // Seconds for identity exchange
static constexpr double HANDSHAKE_TIMEOUT = 30.0; // Seconds for identity exchange (match Columba)
static constexpr double SCAN_INTERVAL = 5.0; // Seconds between scans
static constexpr double PEER_TIMEOUT = 30.0; // Seconds before peer removal
static constexpr double POST_MTU_DELAY = 0.15; // Seconds after MTU negotiation
static constexpr double BLACKLIST_BASE_BACKOFF = 60.0; // Base backoff seconds
static constexpr double ZOMBIE_TIMEOUT = 45.0; // Seconds with no activity before force-disconnect
static constexpr double ADVERTISING_REFRESH_INTERVAL = 60.0; // Seconds between advertising refreshes
}

//=============================================================================
Expand Down Expand Up @@ -111,9 +113,10 @@ namespace Fragment {
static constexpr size_t HEADER_SIZE = 5;

enum Type : uint8_t {
LONE = 0x00, // Single fragment (complete message)
START = 0x01, // First fragment of multi-fragment message
CONTINUE = 0x02, // Middle fragment
END = 0x03 // Last fragment (or single fragment)
END = 0x03 // Last fragment
};
}

Expand Down
4 changes: 1 addition & 3 deletions src/Bytes.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,12 @@ MEM("Creating from data-move...");
MEMF("Bytes object created from data-move \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
// Construct from std::vector<uint8_t> with standard allocator (for MsgPack interop)
// Only needed on Arduino where Data uses PSRAMAllocator (different from std::allocator)
#ifdef ARDUINO
// Needed because Data uses PSRAMAllocator (different from std::allocator)
Bytes(const std::vector<uint8_t>& stdvec) {
MEM("Creating from std::vector<uint8_t>...");
assign(stdvec.data(), stdvec.size());
MEMF("Bytes object created from std::vector \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
}
#endif
Bytes(const uint8_t* chunk, size_t size) {
assign(chunk, size);
MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
Expand Down
Loading