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:
James L
2026-02-23 18:08:29 -05:00
parent e741d1cd64
commit 5ed70dcca9
66 changed files with 18264 additions and 35 deletions

201
lib/microReticulum/src/Bytes.cpp Executable file
View File

@@ -0,0 +1,201 @@
#include "Bytes.h"
using namespace RNS;
// Creates new shared data for instance
// - If capacity is specified (>0) then create empty shared data with initial reserved capacity
// - If capacity is not specified (<=0) then create empty shared data with no initial capacity
void Bytes::newData(size_t capacity /*= 0*/) {
//MEMF("Bytes is creating own data with capacity %u", capacity);
//MEM("newData: Creating new data...");
Data* data = new Data();
if (data == nullptr) {
ERROR("Bytes failed to allocate empty data buffer");
throw std::runtime_error("Failed to allocate empty data buffer");
}
//MEM("newData: Created new data");
if (capacity > 0) {
//MEMF("newData: Reserving data capacity of %u...", capacity);
data->reserve(capacity);
//MEM("newData: Reserved data capacity");
}
//MEM("newData: Assigning data to shared data pointer...");
_data = SharedData(data);
//MEM("newData: Assigned data to shared data pointer");
_exclusive = true;
}
// Ensures that instance has exclusive shared data
// - If instance has no shared data then create new shared data
// - If instance does not have exclusive on shared data that is not empty then make a copy of shared data (if requests) and reserve capacity (if requested)
// - If instance does not have exclusive on shared data that is empty then create new shared data
// - If instance already has exclusive on shared data then do nothing except reserve capacity (if requested)
void Bytes::exclusiveData(bool copy /*= true*/, size_t capacity /*= 0*/) {
if (!_data) {
newData(capacity);
}
else if (!_exclusive) {
if (copy && !_data->empty()) {
//TRACE("Bytes is creating a writable copy of its shared data");
//Data* data = new Data(*_data.get());
//MEM("exclusiveData: Creating new data...");
Data* data = new Data();
if (data == nullptr) {
ERROR("Bytes failed to duplicate data buffer");
throw std::runtime_error("Failed to duplicate data buffer");
}
//MEM("exclusiveData: Created new data");
if (capacity > 0) {
//MEMF("exclusiveData: Reserving data capacity of %u...", capacity);
// if requested capacity < existing size then reserve capacity for existing size instead
data->reserve((capacity > _data->size()) ? capacity : _data->size());
//MEM("exclusiveData: Reserved data capacity");
}
else {
data->reserve(_data->size());
}
//MEM("exclusiveData: Copying existing data...");
data->insert(data->begin(), _data->begin(), _data->end());
//MEM("exclusiveData: Copied existing data");
//MEM("exclusiveData: Assigning data to shared data pointer...");
_data = SharedData(data);
//MEM("exclusiveData: Assigned data to shared data pointer");
_exclusive = true;
}
else {
//MEM("Bytes is creating its own data because shared is empty");
//data = new Data();
//if (data == nullptr) {
// ERROR("Bytes failed to allocate empty data buffer");
// throw std::runtime_error("Failed to allocate empty data buffer");
//}
//_data = SharedData(data);
//_exclusive = true;
//MEM("exclusiveData: Creating new empty data...");
newData(capacity);
//MEM("exclusiveData: Created new empty data");
}
}
else if (capacity > 0 && capacity > size()) {
reserve(capacity);
}
}
int Bytes::compare(const Bytes& bytes) const {
if (_data == bytes._data) {
return 0;
}
else if (!_data) {
return -1;
}
else if (!bytes._data) {
return 1;
}
else if (*_data < *(bytes._data)) {
return -1;
}
else if (*_data > *(bytes._data)) {
return 1;
}
else {
return 0;
}
}
int Bytes::compare(const uint8_t* buf, size_t size) const {
if (!_data && size == 0) {
return 0;
}
else if (!_data) {
return -1;
}
int cmp = memcmp(_data->data(), buf, (_data->size() < size) ? _data->size() : size);
if (cmp == 0 && _data->size() < size) {
return -1;
}
else if (cmp == 0 && _data->size() > size) {
return 1;
}
return cmp;
}
void Bytes::assignHex(const uint8_t* hex, size_t hex_size) {
// if assignment is empty then clear data and don't bother creating new
if (hex == nullptr || hex_size <= 0) {
_data = nullptr;
_exclusive = true;
return;
}
exclusiveData(false, hex_size / 2);
// need to clear data since we're appending below
_data->clear();
for (size_t i = 0; i < hex_size; i += 2) {
uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
_data->push_back(byte);
}
}
void Bytes::appendHex(const uint8_t* hex, size_t hex_size) {
// if append is empty then do nothing
if (hex == nullptr || hex_size <= 0) {
return;
}
exclusiveData(true, size() + (hex_size / 2));
for (size_t i = 0; i < hex_size; i += 2) {
uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
_data->push_back(byte);
}
}
const char hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
const char hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
std::string RNS::hexFromByte(uint8_t byte, bool upper /*= true*/) {
std::string hex;
if (upper) {
hex += hex_upper_chars[ (byte& 0xF0) >> 4];
hex += hex_upper_chars[ (byte& 0x0F) >> 0];
}
else {
hex += hex_lower_chars[ (byte& 0xF0) >> 4];
hex += hex_lower_chars[ (byte& 0x0F) >> 0];
}
return hex;
}
std::string Bytes::toHex(bool upper /*= false*/) const {
if (!_data) {
return "";
}
std::string hex;
for (uint8_t byte : *_data) {
if (upper) {
hex += hex_upper_chars[ (byte& 0xF0) >> 4];
hex += hex_upper_chars[ (byte& 0x0F) >> 0];
}
else {
hex += hex_lower_chars[ (byte& 0xF0) >> 4];
hex += hex_lower_chars[ (byte& 0x0F) >> 0];
}
}
return hex;
}
// mid
Bytes Bytes::mid(size_t beginpos, size_t len) const {
if (!_data || beginpos >= size()) {
return NONE;
}
if ((beginpos + len) >= size()) {
len = (size() - beginpos);
}
return {data() + beginpos, len};
}
// to end
Bytes Bytes::mid(size_t beginpos) const {
if (!_data || beginpos >= size()) {
return NONE;
}
return {data() + beginpos, size() - beginpos};
}